Multiple Initializations of Static Function Variables

Started by
8 comments, last by PistachioPro 18 years, 1 month ago
This has been driving me crazy for a while. I've looked around the forums for an answer, but I haven't found any ... or if I have, I haven't been able to understand them. Here's the deal, I have a template class that basically looks like this:

template< typename tThis >
class TGeometry : public CGeometry
{
public:
    TGeometry( ) : CGeometry( GetClassID() ) { }

    static const tClassID& GetClassID( )
    {
        static tClassID id = ms_GenerateClassID( );
        return id;
    }
};

where tClassID is a typedef for int and ms_GenerateClassID is a static method defined in CGeometry that generates a unique ID each time it's called. The idea behind this class is that it should allow me to create various types of geometry all with unique IDs. For each pair of geometry classes, I also make a TCollider< CGeom1, CGeom2 > which handles the specifics of collision detection between the two types. For each TCollider type, I make a static variable that does some registration stuff and, more importantly, calls TGeometry<??>::GetClassID( ) for both of the geometries the collider deals with. The intention with this code is that ms_GenerateClassID( ) will only be called once for each geometry class. In debug mode, this is indeed the case. In release mode, however, ms_GenerateClassID( ) gets called once before main during the TCollider registration and once in main when I create the first instance of the geometry. Rewriting the GetClassID( ) code in the following two ways makes it work as expected in release mode:

    static const tClassID& GetClassID( )
    {
        static tClassID* id = new tClassID( ms_GenerateClassID( ) );
        return *id;
    }


    static const tClassID& GetClassID( )
    {
        static bool b = true;
        static tClassID id;
        if( b ) { b = false; id = ms_GenerateClassID( ); }
        return id;
    }

My question is why? I believe I've heard people say you should allocate from the heap in this kind of situation, so I'm not too surprised that the first way works (though I don't really know why), but I'm pretty baffled that the second way works. It's seems like it's doing the exact same thing as the original code! Well, I'd appreciate any insight into this mess, Thanks, John Edwards p.s. I'm using MSVC6.
Advertisement
> p.s. I'm using MSVC6.


Upgrade your compiler, get the VS2005 Express Edition for free instead.

MSVC6 and templates mix like chili and chocolate sauce.
Yum.

I've toyed with converting over to VS2005, but everything generally works in MSVC6, and I've got so many variables declared inside for loops I'd rather leave well enough alone.

Does this really just boil down to a template problem in MSVC6, or this there actually some C++ obscura going on?

John Edwards

If I am not completely mistaken the line:

static tClassID id = ms_GenerateClassID( );

is run only once.


For example:

void foo()
{
static int counter = 5;

counter++;
}

the variable counter is initialised only once, so every time the function is called, the counter get a new number instead of resetting it back to 5. Basically, loose the "static", you shouldn't need it.
Quote:Original post by PistachioPro
Does this really just boil down to a template problem in MSVC6, or this there actually some C++ obscura going on?


Well the fact that you're calling GetClassID in TGeometry default constructor's initializer list makes me alittle suspicious, anyways this is what the standard has to say about the order of initialization of local variables of static storage duration:

Quote:Original by ISO-IEC 14882.2003 section 6.7
4 The zero-initialization (8.5) of all local objects with static storage duration (3.7.1) is performed before any other initialization takes place. A local object of POD type (3.9) with static storage duration initialized with constant-expressions is initialized before its block is first entered. An implementation is permitted to perform early initialization of other local objects with static storage duration under the same conditions that an implementation is permitted to statically initialize an object with static storage duration in namespace scope (3.6.2). Otherwise such an object is initialized the first time control passes through its declaration; such an object is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration. If control re-enters the declaration (recursively) while the object is being initialized, the behavior is undefined. [Example:
int foo(int i){static int s = foo(2*i); // recursive call – undefinedreturn i+1;}

—end example]


Without knowing what ms_GenerateClassID does exactly it's hard to say for sure.

Quote:Original post by PistachioPro
... but everything generally works in MSVC6


All the more reason to be worried, you should rely on the C++ ISO standard for correct & portable behaviour not (and especially not) VC++ 6.0 of all compilers.

Quote:Original post by PistachioPro
, and I've got so many variables declared inside for loops I'd rather leave well enough alone.


What does that got to do with anything, you're allowed to declare variables inside the scope of a for-loop.

There is absolute no excuse for usng VC++ 6.0 anymore, it's a poor excuse of a C++ compiler, seriously it's terrible, these are just the list of known issues, everybody else knows it's implementation of the C++ standard library is mediocre (this mostly due to the fact it is a mediocre C++ compiler). Stop using >= ~10 years old technology that even predates C++ ISO standardization.
How about this way to generate IDs at compile-time ?
template <typename T>struct ID{	static int getID()	{		return (int)getID;	}};// examplestruct A : ID<A>{	int test() { return getID(); }};struct B : ID<B>{	int test() { return getID(); }};

Here's ms_GenerateClassID( ):

static tClassID ms_GenerateClassID( ){    static tClassID id=0; return id++;}


I don't think there are any problems with recursion going on.

Now, as far as "everything works in MSVC6" is concerned, I meant my program works. I don't mean to imply that MSVC6 is a C++ compliant compiler. The problem with initializing variables in for loops in MSVC6 is that they're scoped outside of the loop, meaning you end up writing code like:

for( int i=0; i<N; i++ ) { ... }for( i=0; i<M; i++ ) { ... }


which means inserting a whole lot of "ints" to convert to VS2005.

I think I will try porting over enough of the code to VS2005 to see if it still causes problems. In the mean time, I'd still be interested to hear from anyone who has a definitive explanation (or even a good guess) about what's going on.

@nmi: That's an interesting system (though I think potentially dangerous if the compiler decides to lump ID<A>::getID() and ID{B>::getID() into the same address), but it won't work for me. I need the IDs to pack as closely to zero as possible, because I use them to index into an array.

John Edwards
Quote:Original post by PistachioPro
In the mean time, I'd still be interested to hear from anyone who has a definitive explanation (or even a good guess) about what's going on.


Just wondering what happens if your compiler inlines the call to "GetClassID". If inlining is performed in different compilation units, each of them may end up with its own private copy of "static tClassID id".


If the compiler is inlining these functions and using different static data for each instance, that would certainly cause problems. Further, it makes sense that the functions wouldn't be inlined in debug mode (where everything worked) and would in release mode (where the system fails). However, I'm a little surprised that making the small modifications (as described in the original post) would fix the situation ... though I guess it's possible that the small amount of bulk added to the fixed functions would stop the compiler from inlining them. I don't know. Does anyone else?

John Edwards
Well, I've ported everything to VS2005 and the problem went away. It certainly appears like MSVC6 was to blame, and not some obscure C++ rule. I've gotta say I'm a little disappointed it just boiled down to that, but at least it's fixed.

John Edwards

This topic is closed to new replies.

Advertisement