c++ static destructor sequence

0be69bde92e8c5bfb313537ab9d4fe76
0
ElOmmy 101 Feb 09, 2006 at 15:07

Hi
i’ve just come across a really unpleasant problem - static destructors.
what i want is to call an “exit” function after “everything”. this means, after all calls to static destructors.
i’ve tried the atexit C runtime library function, which adds a function pointer to a LIFO exit routine stack, but apparently MS VC++ (2005, havent tried other versions yet) uses the same ‘atexit’ exit routine stack to push a compiler generated function that calls static destructors - and this routine appears to be added somewhere during CRT initialization, assuring it definitely gets called after my custom exit routine.
any ideas?

7 Replies

Please log in or register to post a reply.

Eaa9847123e828897f960de0badf1ffa
0
Alex 101 Feb 09, 2006 at 15:34

you can force a clean up of all static destructors via _cexit().
The function returns after it has finished (unlike the other incarnations of exit which terminate your app).

ALex

6d318bb67270aa12b325e2cd7b64ff7a
0
pater 101 Feb 09, 2006 at 16:51

What I do to circumvent this problem is that I have a base class for all classes that need static destruction (and also static construction).

enum {
  I4_INIT_TYPE_MEMORY_MANAGER,     // main i4 memory manager
  I4_INIT_TYPE_PRIORITY,           // Anything that only relies on memman.
  I4_INIT_TYPE_THREADS,            // initialized thread info

  I4_INIT_TYPE_STRING_MANAGER,
  I4_INIT_TYPE_FILE_MANAGER,
  I4_INIT_TYPE_DLLS,
  I4_INIT_TYPE_BEFORE_OTHER,
  I4_INIT_TYPE_OTHER,
  I4_INIT_TYPE_AFTER_ALL            //For anything that relies upon others
};
class i4_init_class
{
public:
  static i4_init_class *first_init; 
  i4_init_class *next_init;

  virtual int init_type() { return I4_INIT_TYPE_OTHER; }

  virtual void init()   {}
  virtual void uninit() {}

  i4_init_class();
  virtual ~i4_init_class();
};

(the init_type is just an enumeration of initialisation priorities)
Derived classes override the init() or uninit() method (or both), but contain no code (except variable initialisations) in their constructor. The actual code (like memory allocation/deallocation) has been moved to init()/uninit()
CPP file:

void i4_init()
{
  I4_ASSERT(!i4_inited, "i4 already initialized");
  //This is one of the few places where code is not time-critical
  

  for (int t=0; t<=I4_INIT_TYPE_AFTER_ALL; t++)
  {
    i4_init_class *i=i4_init_class::first_init;  
    Current_Init_Class=0;
    for (;i;i=i->next_init)
        {
        if (t==I4_INIT_TYPE_MEMORY_MANAGER)
            {
            Num_Init_Classes++;
            }
        Current_Init_Class++;
        if (i->init_type()==t)//sucht alle init-Typen ab und initialisiert den Richtigen (Reihenfolge)
            i->init();
        }

    if (t==I4_INIT_TYPE_MEMORY_MANAGER)
      i4_inited=i4_T;   // ok to allocate memory after this stage
  }
  i4_warning("Successfully initialized %d system components.",Num_Init_Classes);

}

void i4_uninit()
{
  I4_ASSERT(i4_inited, "i4_uninit() without i4_init()");

  for (int t=I4_INIT_TYPE_AFTER_ALL; t>=0; t--)
  {
    i4_init_class *i=i4_init_class::first_init;  
    for (;i;i=i->next_init)
      if (i->init_type()==t)
        i->uninit();
  }

  i4_inited=i4_F;
}


i4_init_class::~i4_init_class()
{
  i4_init_class *last=0, *i=first_init;

  for (;i && i!=this;)
  {
    last=i;
    i=i->next_init;
  }
  
  if (!i)
    i4_error("couldn't find init to remove");

  if (last)
    last->next_init=next_init;
  else
    first_init=next_init;

  next_init=0;
}



i4_init_class::i4_init_class()
{
  this->next_init=first_init;
  first_init=this;
}

At the beginning of the prg, i call the static i4_init() function, at the end, I call i4_uninit.
This ensures all classes get initialized/deinitialized in a specific order. Since I use a lot more singletons than init_types, some of them still get called in a random order, but I can make sure that they don’t depend on each other. As an example, I don’t care in which order my INIT_TYPE_DLLS-type classes get called, but I care that they’re called after the memory manager is initialized.
Destruction happens in reverse order, of course.

6f0a333c785da81d479a0f58c2ccb203
0
monjardin 102 Feb 09, 2006 at 17:42

That’s great pater, thanks for posting it. :)
With so many post concerning the ambiguities of static/global initialization and destruction, an approach like yours could save a lot people of many hassles.

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Feb 09, 2006 at 19:26

Time for a new code gem? :)

F8ca854545d7687c9201a570d2def65e
0
MJeannig 101 Feb 09, 2006 at 20:51

Actually you should try to avoid complex objets build statically… Why do you need globals ?

6f0a333c785da81d479a0f58c2ccb203
0
monjardin 102 Feb 09, 2006 at 22:07

@MJeannig: You have to initialize your interdependent classes somehow. What problem do you see with pater’s method?

6d318bb67270aa12b325e2cd7b64ff7a
0
pater 101 Feb 09, 2006 at 22:24

It may seem that having a lot of static classes is bad design. In many cases, I completelly agree to this, but the advantage of the method I’m using is, that it adding an additional class can be done without modification of other code. Some of my classes will add themselves to other lists (besides the one used by the i4_init_class itself), like the list of game objects, or the list of user commands. So adding i.e. a new game object type is done by just declaring a static instance of such a class, which then knows how this new object is going to be constructed. With some macro tricks, this is only a single line of code, at the very location where the object itself is declared. I don’t need to have a file where all my objects are listed, and which includes every object’s .h file to be able to call the object’s constructor.