Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Destruction of objects with static storage duration

Tags:

c++

Consider the following program.

struct s { ~s(); };
void f()
{
    static s a;
}

struct t { ~t() { f(); } };
int main()
{
    static s b;
    static t c;
}

I'm trying to figure out what exactly the standard guarantees with respect to the destruction of static objects, but I'm finding the text of C++03[basic.start.term] rather insufficient.

Is the behavior of the program defined? If so, what is the order of destruction of the static objects a, b and c? What would happen if s::~s threw an exception? Please explain your reasoning, preferably with quotes from the standard.

like image 497
avakar Avatar asked Sep 17 '10 14:09

avakar


2 Answers

In the following Objects refers to objects of static storage duration.

Objects in the global namespace are created before main.

Objects in the same compilation unit are created in order of definition.
The order is undefined across different compilation units.

Objects in a namespace are created before any function/variable in that namespace is accessed. This may or may not be before main.

Objects in a function are created on first use.

Objects are destroyed in reverse order of creation. Note the order of creation is defined by the completion of their CONSTRUCTOR (not when it was called). Thus one object 'x' that creates another 'y' in its constructor will result in 'y' being constructed first.

If they were not created then they will not be destroyed.

Is the behavior of the program defined?

So yes the order is well defined

b:   Created
c:   Created
c:   Destroyed Start
a:   Created (during destruction of C)
c:   Destroyed End
a:   Destroyed  (a was created after b -> destroyed before b)
b:   Destroyed

Modify the code to see:

#include <iostream>

struct s
{
    int mx;
    s(int x): mx(x) {std::cout <<  "S(" << mx << ")\n";}
    ~s()            {std::cout << "~S(" << mx << ")\n";}
};
void f()
{
    static s a(3);
}

struct t
{
    int mx;
    t(int x): mx(x) { std::cout << "T(" << mx << ")\n";}
    ~t()
    {                 std::cout << "START ~T(" << mx << ")\n";
                      f();
                      std::cout << "END   ~T(" << mx << ")\n";
    }
};
int main()
{
    static s b(1);
    static t c(2);
}

Output is:

$ ./a.exe
S(1)
T(2)
Start ~T(2)
S(3)
END   ~T(2)
~S(3)
~S(1)
like image 61
Martin York Avatar answered Oct 04 '22 05:10

Martin York


As has been said, order of destructor calls is the exact reverse of order of completion of constructors (3.6.3/1). In other words (3.8/1), stop of lifetime of an object of static storage duration is the reverse of start of lifetime of an object of static storage duration. So it all comes down to when their constructors are called. Suppose that Printer is a type that outputs something in its constructor in the following examples.

Namespace scope

Objects of namespace scope (global and user defined) are in any case created before the first use of any function or variable that is defined in the same translation unit that the object is defined in (3.6.2/3). This deferred initialization (after main was called) must respect the order of definition with respect to other object definitions in the same translation unit of that objects's definition. (3.6.2/1).

Translation unit 1:

void f() { }

extern Printer a;
Printer b("b");
Printer a("a");
extern Printer c;

Translation unit 2:

Printer c("c");
void f();

If we use f, this will not necessarily force creation of c, because f is not defined in the translation unit where c was defined in. a is created after b because it's defined later.

Block scope

Objects of block scope (local statics) are created when control passes through their definition first or when their block is first entered for PODs (6.7/4). Starting lifetime is tried again the next time control passes throgh it if creation couldn't successfully succeed (in case of an exception) (6.7/4).

void g() { static Print p("P"); }
struct A {
  A() { 
    static int n; 
    if(n++ == 0) throw "X"; 
    cout << "A"; 
    g(); 
  } 
};
void f() {
  try { static A a; } catch(char const *x) { cout << x; }
}

This snippet outputs "XAP".

Class scope

For static data members, the same rule applies about order of initialization according to their definition order within the same translation unit (3.6.2/1). This is because the rule for that is formulated as "objects defined in namespace scope..." rather than "objects of namespace scope...". In C++03 deferred initialization (delaying the construction until a variable/function is used from its translation unit) was only allowed for objects of namespace scope, which was not intended. C++0x allows this also for static data members ("non-local variable with static storage duration").


Thus by taking the above rules and taking into account that destruction order is actually determined by completion of constructors rather than by the start of them, we will get to the order ~c ~a ~b.

If s::~s throws an exception, C++0x says that terminate() is called, and you end up having c destroyed and having ended lifetime of a without completing its destructor if it threw the exception. I can't find anything in the C++03 Standard specifying this. It seems to only specifies that for non-local statics and not also for block-scope statics like C++0x does.

like image 42
Johannes Schaub - litb Avatar answered Oct 04 '22 05:10

Johannes Schaub - litb