4

我正在开发一个基于实体的组件系统,并且我正在尝试为组件类型分配某个索引:

static std::size_t getNextTypeId() {
    static std::size_t lastTypeIdBitIdx{0};
    ++lastTypeIdBitIdx;

    // This line produces the output at the end of the question
    std::cout << lastTypeIdBitIdx << std::endl; 

    return lastTypeIdBitIdx;
}

// I'm assuming that TypeIdStorage<T1>::bitIdx will always be different
// from TypeIdStorage<T2>::bitIdx
template<typename T> struct TypeIdStorage { 
    static const std::size_t bitIdx; 
};

// This line statically initializes bitIdx, getting the next id
template<typename T> const std::size_t TypeIdStorage<T>::bitIdx{getNextTypeId()};

在我的游戏代码中,我声明了大约 20 个组件类型,如下所示:

struct CPhysics : public sses::Component { ... };
struct CHealth : public sses::Component { ... };
struct CWeapon : public sses::Component { ... };
// and so on...

在我的实体系统代码中,我多次使用作为组件类型之一 - 我希望这会发生TypeIdStorage<T>::bitIdxT

  • 如果TypeIdStorage<T>存在,只需返回TypeIdStorage<T>::bitIdx
  • 如果它不存在,则创建它并bitIdx使用getNextTypeId().

这是我运行应用程序时打印的内容:

1 2 3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ...

呼叫怎么可能getNextTypeId()返回相同的数字?这种输出应该是不可能的。

难道不能保证静态变量不会重复递增吗?我在这里真的很困惑。


调试发布模式下都使用g++ 4.8.1clang++ 3.4进行了测试。相同的输出。

valgrind不会打印任何有趣的东西。

clang++ 的AddressSanitizer也不打印任何有趣的东西。

将我的程序的入口点设置为int main() { return 0; }产生完全相同的输出。问题出在编译时——但怎么可能呢?这对我来说似乎是一个不可能的情况。

4

2 回答 2

4

static声明函数时需要删除:

std::size_t getNextTypeId() {
    // ...
}

确保仅存在此功能的一个版本。为此,您可能还需要将定义移动到实现文件中,并且只将声明留在标题中。

如果声明 function static,则表示该符号不导出,只能在同一个翻译单元中使用。它不再在翻译单元之间共享。这导致每个翻译单元都有自己的函数副本,当然每个副本都有自己的计数器。

于 2013-10-13T12:15:21.630 回答
3

您没有发布足够的代码来复制问题。但是,如果您在头文件中有上述代码并在多个翻译单元中使用它,您可以获得观察到的行为。在这种情况下,代码的问题是相同的模板代码解析为使用不同的函数,即不同版本的getNextTypeId(). 当然,该问题的解决方法是具有getNextTypeId()bestatic函数,而是在所有情况下使用相同的函数,例如 make it inline。例如:

  1. 头文件(假设在 中dcount.h):

    #include <iostream>
    
    static std::size_t getNextTypeId() {
        static std::size_t lastTypeIdBitIdx{0};
        ++lastTypeIdBitIdx;
    
        // This line produces the output at the end of the question
        std::cout << "last index=" << lastTypeIdBitIdx << '\n';
    
        return lastTypeIdBitIdx;
    }
    
    // I'm assuming that TypeIdStorage<T1>::bitIdx will always be different
    // from TypeIdStorage<T2>::bitIdx
    template<typename T> struct TypeIdStorage { 
        static const std::size_t bitIdx; 
    };
    
    // This line statically initializes bitIdx, getting the next id
    template<typename T> const std::size_t TypeIdStorage<T>::bitIdx{getNextTypeId()};
    
  2. 第一个翻译单元(假设在dcount-t1.cpp):

    #include "dcount.h"
    
    struct A {};
    struct B {};
    struct C {};
    
    int f()
    {
        TypeIdStorage<A>::bitIdx;
        TypeIdStorage<B>::bitIdx;
        TypeIdStorage<C>::bitIdx;
    }
    
  3. 第二个翻译单元(假设在dcount-t2.cpp):

    #include "dcount.h"
    
    struct D {};
    struct E {};
    struct F {};
    
    int g()
    {
        TypeIdStorage<D>::bitIdx;
        TypeIdStorage<E>::bitIdx;
        TypeIdStorage<F>::bitIdx;
    }
    
  4. 最后是一个将这些组合在一起的程序(dcount-main.cpp):

    extern void f();
    extern void g();
    
    int main()
    {
        f();
        g();
    }
    

使用例如编译这些文件g++ -std=c++11 -o dcount dcount-t1.cpp dcount-t2.cpp dcount-main.cpp产生一个可执行文件,该可执行文件复制您注意到的行为:

$ g++ -std=c++11 -o dcount dcount-t1.cpp dcount-t2.cpp dcount-main.cpp 
$ ./dcount 
last index=1
last index=2
last index=3
last index=1
last index=2
last index=3
于 2013-10-13T12:14:54.630 回答