Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static members being destroyed while there are still outstanding instances?

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.

like image 968
RJinman Avatar asked Nov 21 '25 07:11

RJinman


2 Answers

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.

like image 101
Mikael Persson Avatar answered Nov 23 '25 20:11

Mikael Persson


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!

like image 36
Lightness Races in Orbit Avatar answered Nov 23 '25 22:11

Lightness Races in Orbit



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!