3

I need to access a static data member from a destructor, but on program exit it seems that it cannot be guaranteed to still exist! For some reason, the static members are being destroyed whilst there are still outstanding instances of the class. It's odd because I've never heard the advice "Never access static members from a destructor" before, and yet I think I'd know about such a limitation if it existed.

I'll give a concrete example:

class MyClass {
    public:
        ~MyClass() { m_instances.erase(m_name); }

    private:
        long m_name;
        static std::map<long, MyClass*> m_instances;
};

In another class, I tried the following nasty hack which appeared to work, though when I think about it I don't think it's really a solution at all.

class MyClass {
    friend class Switch;

    public:
        ~MyClass() { if (m_alive) m_instances.erase(m_name); }

    private:

        static bool m_alive;
        class Switch {
            ~Switch() { MyClass::m_alive = false; }
        };
        static Switch m_switch;

        long m_name;
        static std::map<long, MyClass*> m_instances;
};

What if an instance of MyClass is destroyed after m_instances but before m_switch?? And even if m_switch dies first, the boolean m_alive might have been "destroyed" and therefore possibly overwritten to 'true' (unlikely, I know).

So can anyone offer a better solution? I expect I am missing something very obvious here.

RJinman
  • 603
  • 4
  • 15
  • Seems odd to me to. What compiler / platform do you experience this? – dronus May 09 '11 at 21:20
  • What is it that is holding on to instances of `MyClass` at program exit such that their destructors are being run? – quamrana May 09 '11 at 21:21
  • I wonder if it's a situation similar to this: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.16 – Fred Larson May 09 '11 at 21:24
  • 1
    What `m_switch` are you talking about? There's no `m_switch` in the above code. – AnT stands with Russia May 09 '11 at 21:24
  • The advice should be - "never access anything but member variables from a destructor". –  May 09 '11 at 21:31
  • @quamrana Each MyClass instance is owned by a boost::shared_ptr object. – RJinman May 09 '11 at 21:34
  • 1
    Surely you can access global and static variables from a destructor. – Gunther Piez May 09 '11 at 21:35
  • @Rob: And who owns the shared_ptr? I'm trying to establish when the `MyClass` destructor is called in relation to the destruction of the static `m_instances` – quamrana May 09 '11 at 21:36
  • @drhirsch You certainly *can*. The whole point of giving advice in areas like this is whether you *should*. –  May 09 '11 at 21:37
  • @quamrana The shared_ptr is owned by a factory class and also possibly by multiple EEvent objects which sit in an event queue. The idea is that when the MyClass object is no longer needed (deleted from the factory class) and when there are no outstanding EEvent objects storing a shared_ptr to the object, it will die naturally. By the time it does die though, the static member m_instances is sometimes gone. – RJinman May 09 '11 at 21:42
  • Do you have global or static objects of type MyClass? In that case you would have no control over the order of destruction – Gunther Piez May 09 '11 at 21:51
  • @Rob: Have you seen this answer: http://stackoverflow.com/questions/335369/finding-c-static-initialization-order-problems/335746#335746 among others? – quamrana May 09 '11 at 21:54
  • @drhirsch: I'm assuming that there are no static objects of MyClass since there is a factory class that builds them all. I think its probably some other class (maybe the factory) which is itself static and holds onto one of the shared_ptrs and is destroyed after m_instances. – quamrana May 09 '11 at 22:05

3 Answers3

2

There is no guarantee that static members are destroyed only after all instances of an object of the same class. C++ incorporates no reference counting paradigm (shared_ptr notwithstanding).

When considering lifetime, consider your static members as any other static object. There's really nothing binding them to their containing class other than being in the class's "namespace" (warning: not accurate terminology).

So, if your myClass instances are created statically too, then you need to consider normal static object lifetime rules between them:

Example #1:

#include <iostream>

struct A {
    A() { std::cout << "*A "; };
   ~A() { std::cout << "~A "; };
};

struct B {
    B() { std::cout << "*B "; };
   ~B() { std::cout << "~B "; };

    static A a;
};

B t;
A B::a;

int main() {}

// Output: *B *A ~A ~B 

As you can see, the static member of B is destroyed before the actual instance of B.

Example #2:

#include <iostream>

struct A {
    A() { std::cout << "*A "; };
   ~A() { std::cout << "~A "; };
};

struct B {
    B() { std::cout << "*B "; };
   ~B() { std::cout << "~B "; };

    static A a;
};

A B::a;
B t;

int main() {}

// Output: *A *B ~B ~A 

Here the reverse is true. It's a cheap fix for your current issue, but my advice is to avoid static instances altogether; you'll only fall into more static initialisation pitfalls like this down the line... possibly with a future incarnation of the same code!

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • This will only happen to global objects and only when the program ends. That an object is encapsulated as an static object in another namespace doesn't change the fact that it is basically a global object – Gunther Piez May 09 '11 at 21:49
  • @drhirsch: I just said all that! (Your use of "global" is not right, though.) – Lightness Races in Orbit May 09 '11 at 21:50
  • Well yes :-) But I thought it is was not relevant, because I couldn't imagine the OP would use a global MyClass, possibly create a static member after the global MyClass and then ask why he gets problems accessing a static member... Now I am not so sure anymore ;-) – Gunther Piez May 09 '11 at 21:57
  • @drhirsch: My answer is based on the educated guess that that is the cause of the problem. :) After all, if it's not the case, then this scenario should not be occurring. – Lightness Races in Orbit May 09 '11 at 21:58
2

This is clearly a problem of static destruction order. I would recommend something like the following:

class MyClass {
    public:
        MyClass() { add_instance(m_name, this); };
        ~MyClass() { erase_instance(m_name); }

    private:
        long m_name;
        static std::map<long, MyClass*>& get_instance_map();
        static void add_instance(long aName, MyClass* aObj);
        static void erase_instance(long aName);
};

std::map<long, MyClass*>* MyClass::get_instance_map() {
  static std::map<long, MyClass*>* p_inst = new std::map<long, MyClass*>();
  return p_inst;
};

void MyClass::add_instance(long aName, MyClass* aObj) {
  static std::map<long, MyClass*>* p_inst = MyClass::get_instance_map();
  p_inst->insert( std::make_pair(aName, aObj) );
};

void MyClass::erase_instance(long aName) {
  static std::map<long, MyClass*>* p_inst = MyClass::get_instance_map();
  p_inst->erase( aName );
};

If you need the instance map to be deleted, it might not be possible. Otherwise, just use a normal construct-on-first-use idiom. The point here is that the map is a heap-allocated std::map object, and not deleting it just means that it will be flushed away as the OS reclaims the freestore memory which will occur after every other "normal" execution, like a destructor call.

Mikael Persson
  • 18,174
  • 6
  • 36
  • 52
  • @Mikael: What happens if just one MyClass gets constructed then immediately destroyed - but then another MyClass gets constructed? Have you lost your map? – quamrana May 09 '11 at 21:57
  • Oh yeah, that's true! How could I have missed that. I'll fix an EDIT. – Mikael Persson May 09 '11 at 22:02
  • @Mikael: I think you've used the static pointer in a function idiom, but that is supposed to 'leak' the instances it creates on purpose. – quamrana May 09 '11 at 22:06
  • This seems to work fine. Thank you. Is there an official way to mark this question as 'solved'? – RJinman May 09 '11 at 22:23
  • @Rob: Click on the big tick mark below the votes for the answer you like. – quamrana May 09 '11 at 22:26
0

If you are having these kinds of problems with a static, it must mean that MyClass also has static scope and you can't design code like this with one static accessing the other. It might work and it might not work since you have a problem with the order of destruction.

It's also entirely possible that you have another global (static) causing memory corruption. If this is the case it might mean that overwriting one global might overwrite other near by globals residing in the same memory space, i.e. the static you are having problems with has been corrupted and not deleted.

rtn
  • 127,556
  • 20
  • 111
  • 121