The "static counter" for types behaves strangely

I am developing an entity-based system, and I am trying to assign a specific index to component types:

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()}; 

In my game code, I have about 20 types of components declared as follows:

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

In my system object code, I use TypeIdStorage<T>::bitIdx with T one of the component types several times - I expect this to happen:

  • If TypeIdStorage<T> exists, just return TypeIdStorage<T>::bitIdx .
  • If it does not exist, create it and initialize bitIdx with getNextTypeId() .

This is what prints when the application starts:

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

How is it possible that a call to getNextTypeId() returns the same number? This type of conclusion should be impossible.

Does it that the static variable will increase without repetition? I am really confused here.


Tested with g ++ 4.8.1 and clang ++ 3.4 , both in debug mode and in release mode. The same way out.

valgrind doesn't print anything interesting.

clang ++ AddressSanitizer does not print anything interesting.

Setting the entry point of my program to int main() { return 0; } int main() { return 0; } gives exactly the same result. The problem at compile time - but how is this possible? It seems to me that the situation is impossible .

+4
c ++ static static-methods c ++ 11 static-members
source share
2 answers

When declaring a function, you need to abandon static :

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

to make sure that there is only one version of this function. To do this, you probably also need to transfer the definition to the implementation file and leave the declaration in the header.

If you declare a static function, this means that the character is not exported and can only be used in the same translation unit. It is no longer split between translation units. This leads to the fact that each translation unit has its own copy of the function, and each copy has, of course, its own counter.

+4
source share

You have not posted enough code to replicate the problem. However, if you have the above code in the header file and use it from multiple translation units, you can get the observed behavior. The problem with the code in this case is that the same template code allows you to use different functions, that is, different versions of getNextTypeId() . Correction of the problem, of course, should not have the getNextTypeId() function static , but rather use the same function in all cases, for example, make it inline . For example:

  • Header file (assumed to be in 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()}; 
  • first translation unit (assumed to be in dcount-t1.cpp ):

     #include "dcount.h" struct A {}; struct B {}; struct C {}; int f() { TypeIdStorage<A>::bitIdx; TypeIdStorage<B>::bitIdx; TypeIdStorage<C>::bitIdx; } 
  • second translation unit (assumed to be in dcount-t2.cpp ):

     #include "dcount.h" struct D {}; struct E {}; struct F {}; int g() { TypeIdStorage<D>::bitIdx; TypeIdStorage<E>::bitIdx; TypeIdStorage<F>::bitIdx; } 
  • Finally, a program that combines them ( dcount-main.cpp ):

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

Compiling these files using, for example, g++ -std=c++11 -o dcount dcount-t1.cpp dcount-t2.cpp dcount-main.cpp gives an executable file that replicates the behavior you notice:

 $ 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 
+3
source share

All Articles