类型的“静态计数器”表现得很奇怪

时间:2013-10-13 11:38:10

标签: c++ c++11 static static-methods static-members

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

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   五   6   7   8   9   10   11   12   13   14   15   16   ...

如何调用getNextTypeId()返回相同的号码?这种输出应该是不可能的。

不是保证静态变量会不会重复增加吗?我真的很困惑。


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

valgrind 不会打印任何有趣的内容。

clang ++的 AddressSanitizer 也不会打印任何有趣的内容。

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

2 个答案:

答案 0 :(得分:4)

在声明函数时需要删除static

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

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

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

答案 1 :(得分:3)

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

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

    dcount.h
  2. 第一个翻译单元(假设在#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()}; 中):

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

    dcount-t2.cpp
  4. 最后一个程序将这些一起拉出来(#include "dcount.h" struct D {}; struct E {}; struct F {}; int g() { TypeIdStorage<D>::bitIdx; TypeIdStorage<E>::bitIdx; TypeIdStorage<F>::bitIdx; } ):

    dcount-main.cpp
  5. 使用例如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