Click here to Skip to main content
15,352,892 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Why do I crash in DeserializableClassMapEntry() when using DESERIALIZABLE_DEFINITION( CObject ) in source files alphabetically lower than the file that has the following code and what is the proper way to fix it?

Is the problem from using the pre-processor macros or is this the static initialization fiasco? Is it OK that my DeserializableClassMapEntry() is in the header file?

I can fix this bug by putting the macro for objects lower alphabetically in a parent source file higher alphabetically. I would guess this would not be portable.

All of this code is in the header file of the base class for all of the classes that use it. The static map deserializedObjectFunctionMap is defined in the base class source, or .cpp file.

typedef void (*pReturnDeserializedObjectFunction)( std::istream & input, CDocElement ** ppEl );

typedef std::map< const CString, pReturnDeserializedObjectFunction > DeserializedObjectFunctionMap;


class DeserializableClassMapEntry
{
public:
    static DeserializedObjectFunctionMap deserializedObjectFunctionMap;
    DeserializableClassMapEntry( const char * classNameString, pReturnDeserializedObjectFunction returnDeserializedObjectFunction )
    {
        deserializedObjectFunctionMap[ classNameString ] = returnDeserializedObjectFunction;
    }
};

// the two macros
#define DESERIALIZABLE( classNameString )\
public:\
    static DeserializableClassMapEntry mapEntry;\
    static void DeserializeThis( std::istream & input, CDocElement ** ppElement );\


#define DESERIALIZABLE_DEFINITION( classNameString )\
    DeserializableClassMapEntry classNameString::mapEntry( #classNameString, &classNameString::DeserializeThis );\
    void classNameString::DeserializeThis( std::istream & input, CDocElement ** ppElement )\
    {\
        *ppElement = new classNameString;\
        input >> *(classNameString*)*ppElement;\
    };\           


Update:

So what I'm going to do is put all those macro definitions in the .cpp file of the base class (the definitions for elements alphabetically lower than that base class file, which houses the static function map), after the static function map definition. That way I force correct initialization order because they are in the same file (Eckel).

One difficult thing about this bug was that the static function map looked like it was good, even though it probably wasn't. But it goes against my grain to have a class that will end up embedding definitions from many classes. That's why I will await a good answer. Maybe you will say my choice was good, but hopefully I'm missing something.

It was weird to realize I've never said anything like "new vector" and so the "first use idiom" put-"new"-static-in-a-fuction-and-return-a-reference didn't seem to fit as a solution for this problem. Neither did taking out static-ness. But it would be great if there were still a fix for my embedded containers. I'll be here. For a long time hopefully.
Posted
Updated 8-Oct-10 21:29pm
v7

Non-local objects with static lifetime duration in the same translation unit (source file) are initialized in the order of their definition in that translation unit, not in alhphabetical order. The de-initialization occurs in reverse order. The initialization order across translation units is unspecified - i.e. does not depend on the lexicographic ordering of the source file names, or something like that.

If I understand correctly, you rely on the fact that non-locals in files with 'alphabetically lower' names will be initialized before non-locals in source files with lexicoraphically larger names..?! Don't do it, this will result in the typical static order initialization fiasco. One way to go would be the following:

class DeserializableClassMapEntry {
 private:

  static DeserializedObjectFunctionMap& GetObjectMap() {
   static DeserializedObjectFunctionMap sMap;
   return sMap;
  }
  
 public:

  DeserializableClassMapEntry( 
   const char * classNameString, 
   pReturnDeserializedObjectFunction returnDeserializedObjectFunction) {
  GetObjectMap()[ classNameString ] = returnDeserializedObjectFunction;
}

};


You could yous a similar pattern for taking care of proper initialization of the mapEntry static class objects.
   
v3
Comments
Brian Bennett 11-Oct-10 5:34am
   
Thank you for the response. I had actually gotten as far the above. I didn't have the confidence. I couldn't figure out how to do it with mapEntry. The only time it's ever used is in the definition where it's initialized in global space and done so with the preprocessor. I'm trying to find a good way to do it without losing the convenience of the macro so it will have to be in the macro(?). Your answer inspired me to try again. I'll submit an answer if I figure it out, or maybe someone better than me can whip something up.

Update:

I tried but couldn't do it. I couldn't communicate well about the lexicographical part about it. It's what made the bug difficult because it worked in some files and not others but I'd guess it was .net's way of initializing static variables. Of course I should put in all of those definition macros in one place if any, not just part of them. So how do you all feel about that? Is it cool to put all the definitions in a file instead in their own .cpp since they're doubly static? My instinct tells me it's bad practice but I don't know. I hope you say it's OK.
Brian Bennett 23-Oct-10 8:05am
   
Update 2:

I tried the solution above again. Third time is a charm. It's working! It had nothing to do with the macros and preprocessor. I'm not sure how I had gotten away without fixing it. But it is strange how having a local static like that becomes permanent. Makes me wonder if I should make the static map const, or is that overkill?

(I found I could edit these comments by leaving and then navigating back)
Paul Michalik 23-Oct-10 8:21am
   
No, you cannot make the map const since it needs to be updated by the initializers of your registration objects. There are two problems with this solution:
(1) the initialization of such "Meyer's singleton" is not controllable in concurrent environments
(2) you do not have control over the destrction of the map instance. This is completely up to the runtime.

If this does not matter, you are fine.
C++
static void DeserializeThis( std::istream & input, CDocElement ** ppElement );\

The '\' here is redundant.
   
Comments
Brian Bennett 6-Oct-10 7:32am
   
...but doesn't fix the bug.
Paul Michalik 9-Oct-10 7:11am
   
it's not a bug it's a feature (see below)
It will work independently on definition order, if you: (1) initialize the map as above, (2) take care that the initializers for your registrars are run at all, (3) if concurrency is not considered and (4) you do not have any further dependencies among the DeserializableClassMapEntry objects... Potential issues could result from using dlls and/or depending on the structure of your program.

Is your program crashing (is it at all?) during the initialization phase, or during the deserializatio (before or after main is entered)? You mentioned .net, what is the context here? Or are you asking about this desing in general?
   
Comments
Brian Bennett 13-Oct-10 5:10am
   
I'm asking about design in general. It started crashing during the initialization phase in the definition macro when I added it to a new class. I was stumped because it had worked for the other classes before. But now I'm putting all of the definition macros in the one .cpp file which is the base class for all the elements and it's where I define the static DeserializedObjectFunctionMap and now it never crashes. I'd like to better my skills and know if this is good practice or if there are better ways.

It's tricky because the classes have to register themselves very early, hence initialization in the global scope, because everything needs to be in place before the deserialization so soon begins. Simply, the map holds pointers for the deserialize function of every class and I would guess it does that during compilation when the macro is expanded or something close to that. I believe it follows the rules you mentioned. The reason for creating the factory was to manage the dependencies of objects (different things may depend on the same object) during file in and out so I don't think there will be dependencies outside of that that will get int he way. Thanks, Paul!
Paul Michalik 13-Oct-10 13:50pm
   
Hm, you are writing the the past form, so does this mean that it is not crashing anymore? It definitely can not crash because of the std::map initialization, if you rewrote DeserializableClassMapEntry as I suggested.

Generally, this kind of 'pluggable' type-dependent information has several drawbacks:
(1) It depends on too much stuff which the standard calls 'implementation defined'. For example, it requires to structure your program to be such that the registrar symbols are not kicked out by the various optimization strategies: Try to compile the auto-deserializable classes into a separate library (static or dynamic) and link them to a test executable. You will see, that depending on what is seen by the program, some registrars get initialized others get not.

To be continued...
Brian Bennett 16-Oct-10 5:20am
   
My program doesn't crash now. I solved that by putting all of the definition macros in one file to force the initialization order for the static variables. That seems bad to me but it works. I'm interested in your comment about optimization strategies. I didn't try making a separate library because I'm guessing you made that comment under the assumption that my program was crashing.
Paul Michalik 17-Oct-10 5:06am
   
Oh, I forgot I cannot edit comments. Anyway, I point you to the second paragraph of my previous comment. The pattern, or rather, idiom you are using is called 'pluggable-factories' in the c++ world. I recommend to google for that phrase and read a bit about the common problems.

With regard to your specific problem: Again, there will be no static intialization fiasco if you assure, that the shared map instance gets initialized before any of the DeserializableClassMapEntry objects try to access it. This can be done as sketched above. Then, there is no reason to put all the class statics definitions into one file. The order in which the DeserializableClassMapEntry instances are initialized is unspecified, but it should not matter. The important thing is that they are initialized at all, which depend on the structure of your program, as mentioned above. The problem is that the linker might decide to optimize your DeserializableClassMapEntry instances away completely, if there is not a explicit usage of them. The tricky part of c++ pluggable factory design is to assure that this does not happen.

The macros are quite ugly, I would recommend to completely avoid usage of macros in a c++ program. This is rarely necessary and can be in most cases replaced with type safe and much more powerful template formulation.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900