rthur
rthur

Reputation: 1534

Order of static destructors

If a class Foo has a static member variable Bar, I would expect Bar's destructor to run only after the last instance of Foo's destructor runs. This doesn't happen with the code snippet below (gcc 6.3, clang 3.8):

#include <memory>
#include <iostream>

class Foo;
static std::unique_ptr<Foo> foo;

struct Bar {
    Bar() {
        std::cout << "Bar()" << std::endl;
    }

    ~Bar() {
        std::cout << "~Bar()" << std::endl;
    }
};

struct Foo {
    Foo() {
        std::cout << "Foo()" << std::endl;
    }

    ~Foo() {
        std::cout << "~Foo()" << std::endl;
    }

    static Bar bar;
};
Bar Foo::bar;

int main(int argc, char **argv) {
    foo = std::make_unique<Foo>();
}

Outputs:

Bar()
Foo()
~Bar()
~Foo()

Why is the order of destruction not the reverse of construction here? If ~Foo() uses Foo::bar this is a use after delete.

Upvotes: 14

Views: 2643

Answers (5)

In general, you should not write code which depends on the order of construction or destruction of static (or global) data. This makes unreadable and unmaintainable code (you might prefer static smart pointers, or explicit initialization or startup routines called from main). And AFAIK that order is not specified when you link several translation units.

Notice that GCC provides the init_priority and constructor (with priority) attributes. I believe you should rather avoid using them. However, the __attribute__(constructor) is useful inside plugins, for plugin initialization.

In some cases, you might also use atexit(3) (at least on POSIX systems). I don't know if such registered functions are called before or after destructors (and I think you should not care about that order).

Upvotes: 0

Passer By
Passer By

Reputation: 21131

Static objects' lifetimes are based solely on the order of their definition. The compiler doesn't "know enough" when to call Bar::Bar() as much as calling Bar::~Bar().

To illustrate the problem better, consider this

class Foo;

struct Bar {
    Bar() {
        std::cout << "Bar()" << std::endl;
    }

    ~Bar() {
        std::cout << "~Bar()" << std::endl;
    }

    void baz() {}
};

struct Foo {
    Foo() {
        bar.baz();
        std::cout << "Foo()" << std::endl;
    }

    ~Foo() {
        std::cout << "~Foo()" << std::endl;
    }

    static Bar bar;
};

Foo foo;
Bar Foo::bar;

int main() {}

Prints

Foo()
Bar()
~Bar()
~Foo()

The addition of std::unique_ptr postpones Foo::Foo() after its construction in main, giving the illusion of the compiler "knowing" when to call Bar::Bar().

TLDR Static objects should be defined later than its dependencies. Before defining bar, it is just as much a bug to define a std::unique_ptr<Foo> and to define a Foo

Upvotes: 1

Pete Becker
Pete Becker

Reputation: 76245

The complication here is that the code doesn’t instrument the constructor of foo. What happens is that foo gets constructed first, than Foo::bar gets constructed. The call to make…unique constructs a Foo object. Then main exits, and the two static objects get destroyed in reverse order of their construction: Foo::bar gets destroyed, then foo. The destructor for foo destroys the Foo object that it points to, which is the one created inmain.

Upvotes: 2

songyuanyao
songyuanyao

Reputation: 172924

I would expect Bar's destructor to run only after the last instance of Foo's destructor runs.

No, as a static data member, Foo::bar is independent of any instances of Foo.

And for the code you showed,

static std::unique_ptr<Foo> foo; // no Foo created here

Bar Foo::bar; // Foo::bar is initialized before main(), => "Bar()"

int main(int argc, char **argv) {
    foo = std::make_unique<Foo>(); // an instance of Foo is created, => "Foo()"
} 

// objects are destroyed in the reverse order how they're declared
// Foo::bar is defined after foo, so it's destroyed at first => "~Bar()"
// foo is destroyed; the instance of Foo managed by it is destroyed too => "~Foo()"

Upvotes: 3

nwp
nwp

Reputation: 9991

In C++ the objects are constructed in order of occurrence and destructed in the reverse order. First comes foo construction, then bar construction then main is executed then bar is destructed and then foo. This is the behavior you are seeing. The switch appears because the constructor of foo doesn't construct a Foo, it constructs an empty unique_ptr, so you don't get to see Foo() in the output. Then bar is constructed with the output and in main you create the actual Foo after foo has long been constructed.

Upvotes: 11

Related Questions