Sign in to follow this  
Leadorn

class inheritance and static var

Recommended Posts

Leadorn    100
Hi I have a problem with a static member in a class a that is used in class b that inheritance class a. In class b I have a static func1 that uses class a static variable factory_map. Everything linkes but the program craches. in func1 the factory_map variable is null, it isn’t there. This problem comes and goes, sometimes it works sometimes it doesn’t. Is this supported in c++, static var that is uses in an static function in a inheritance class.
header a.h
class a
{
public:
static hashmap<adt::string,void *> factory_map;
}

implement a.cpp
hashmap<adt::string,void *> a::factory_map;

implementation b.h
class b : public a
{
public:
 static void func1();

};

implement class b.cpp
void b::func1()
{
// in hashmap operator[] this pointer is null.
 factory_map["asd"] = something;
}



Share this post


Link to post
Share on other sites
You are probably calling b::func1() before main starts or after it ends (IE you may call it in the constructor of a global object). The problem with this is that order of construction of the static hashmap is not defined with respect to multiple translation units, so you can't be guaranteed that your object is being constructed after the hashmap is constructed which can inadvertantly cause you to access the hashmap when its data is uninitialized. The reason the problem only occurs sometimes after you build your projects is because occassionally the construction order is as you'd want while other times it is not -- it depends on how your project builds. In order to guarantee that the object is always constructed prior to using it, make it a static member of a function instead of a static member of a class. Have the function return a reference to the hashmap. This works because you are guaranteed that the first time the function is called the hashmap is constructed, so func1 or any other function which needs to use the hashmap can do so at any time prior to main.

However, before you go and implement that, understand that while it solves order of construction, order of destruction is still undefined, so, for instance, if you access the hashmap from the destructor of a global object then you aren't guaranteed that you will be accessing the hashmap prior to its destruction (the reverse of the previously mentioned problem).

If you are doing something along those lines, in order to be 100% certain that the hashmap is always constructed when you use it (not uninitialized and not destructed), your best bet is to use reference counting -- whenever an object is initialized that needs the hashmap, add 1 to the reference count of the hashmap. if the count was 0, dynamically allocate the hashmap. When an object that needs the hashmap is uninitialized, decrease the reference count. If the reference count is 0, then delete the hashmap.

This way, you can work with the hashmap both prior to main being called and after main ends and you are always guaranteed that it is constructed and doesn't get destructed until no other objects need it.

Share this post


Link to post
Share on other sites
darookie    1441
If I didn't misinterpret the standard, static data members get external linkage by default, so different compilation units should be a non-issue here. They're also always constructed even if there is no class instance. Initialisation, however, takes place on first-access.

have you tried expcilitly accessing the member using its fully-qualified name?

void b::func1()
{
a::factory_map["asd"] = something;
}



What compiler are you using? VC++6 for example, is known to incompliant with some parts of the ISO C++ standard.

Share this post


Link to post
Share on other sites
Quote:
Original post by darookie
If I didn't misinterpret the standard, static data members get external linkage by default, so different compilation units should be a non-issue here. They're also always constructed even if there is no class instance. Initialisation, however, takes place on first-access.

They do have external linkage and that's precisely what inadvertantly causes the problem. Externed globals are not initialized on first use as you claim, they are actually initialized at an undefined time prior to main, only guaranteed to be initialized in an order relative to other objects in the same exact translation unit. With multiple translation units you can never be certain which object is constructed first unless you have a class with no non-POD datamembers and use the default constructor or copy constructor, or if you construct via an initialization list having all compile-time constants (in which case you are guaranteed it is initialized prior to objects which have non-trivial construction). The error has nothing to do with the compiler potentially being non-compliant, this is completely standard behaviour.

As for an example as to how to be completely safe:

//somewhere in header a.h

class a
{
public:
typedef hashmap<adt::string,void *> factory_map_type;
public:
static factory_map_type& add_ref();
static factory_map_type& unsafe_get();
static void release();
private:
static factory_map_type* factory_map_m;
static unsigned int reference_count_m;
};

//somewhere in a.cpp
a::factory_map_type& a::add_ref()
{
if( reference_count_m == 0 )
factory_map_m = new factory_map_type;

++reference_count_m; // You should really check for overflow here and throw if it overflows

return *factory_map_m;
}

a::factory_map_type& a::unsafe_get()
{
// ToDo: Check for error

return *factory_map_m;
}

void a::release()
{
if( reference_count_m == 1 )
delete factory_map_m;
else
if( reference_count_m == 0 )
{
// Error here, you'll probably want to throw an exception
}

--reference_count_m;
}

a::factory_map_type* a::factory_map_m = 0;

unsigned int a::reference_count_m = 0;

int main()
{
a::factory_map_type& your_map = a::add_ref();

// Do stuff with the map

a::release();
}




That's a barebones version. It would be smart to make an object type which addrefs during construction and releases during destruction, then privately inherit from that type in an object which needs the hashmap. That way you, have exceptions-safe reference counting. Also, if you only ever access the hashmap from inside instances of a or instances of children of a, you can build the mechanism into the base a object so that users can be oblivious to the reference counting and still have it be exception-safe. That way, all the user is concerned with is calling a member function to get the object without worrying about add_ref or release.

Sorry if there is an error there, I have to run out to go shopping so I didn't have time to double-check my code. I'll be back in an hour or two. I can make an example of the exception-safe user objects if you'd like when I come back.

Share this post


Link to post
Share on other sites
darookie    1441
I don't get it. Since b::fun1 cannot be called before main other than in a another static's constructor, the hasmap must have been initialised - or am I missing something here?
Regardless of the - indeed - undefined order of construction.

Please clear that up for me...

Share this post


Link to post
Share on other sites
Leadorn    100
and im using this factory_map before main.

all objects are registering themselves in the factoymap with its _##name##_ and factory function before main. The factory function will factory this class if called.

We have done it this way because we want objects to be serialized and deserialized over sockets or to file.

Share this post


Link to post
Share on other sites
Quote:
Original post by darookie
I don't get it. Since b::fun1 cannot be called before main other than in a another static's constructor, the hasmap must have been initialised

Why do you assume that the hashmap must have been initialized? You have absolutely no way of knowing if it was initialized or not prior to your pre-main function call unless you are in the same translation unit as the hashmap's definition and your call appears after the static member's definition. The function can be called prior to hashmap's constructor since you have no way of knowing the order in which the objects are initialized, which is why you have to use another solution such as the one I have provided.

Also, there are other ways of calling it other than another object's constructor such as within an initialization statement.

Share this post


Link to post
Share on other sites
darookie    1441
I've been using factory classes a lot and never had such problems.
But then again I used a templated singleton holder to wrap my factory classes, so I guess that prevented this from happening.

Share this post


Link to post
Share on other sites
Quote:
Original post by darookie
I've been using factory classes a lot and never had such problems.
But then again I used a templated singleton holder to wrap my factory classes, so I guess that prevented this from happening.

Possibly, yes. If the singleton itself uses reference counting then the problem will be solved, however, if your singleton doesn't use reference counting, but rather, just uses a static object inside of a function, then all that will be solved is order of construction. You will still have undefined order of destruction.

So, if you're not calling an addref and release style function or if you aren't using a smart pointer type for the singleton, then your order of destruction is must-likely not defined so you should switch what you are doing if you need to use the object after main finishes execution (IE inside a global or member/function static object's destructor).

My personal singleton implementation uses reference counting with smart pointers, but unfortunately, most examples I see people give do not, so be extremely careful. Odds are, you probably have undefined order of destruction which can be just as dangerous as undefined order of construction.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this