Destruction Order of Static Objects in C++

Destruction order of static objects in C++

The static objects are destructed in the reverse order of construction. And the order of construction is very hard to control. The only thing you can be sure of is that two objects defined in the same compilation unit will be constructed in the order of definition. Anything else is more or less random.

Ensure the construction and destruction order of static variables in c++

In your case you are using construct on first use, and none of the constructors of your classes dependent on the other. Thus the order of initialization is guaranteed A then B.

The destruction order in this case it is guaranteed to be as such B->A, as long as you have simple destructors. Here is a more elaborate answer.

Why is the order of destruction of these function-local static objects NOT the inverse of their order of initialization?

The actual Standard text in C++14 [basic.start.term] is:

If the completion of the constructor or dynamic initialization of an object with static storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first. [Note: This definition permits concurrent destruction. —end note ]

In your code, two is constructed during the constructor of one. Therefore the completion of constructor of two is sequenced-before the completion of constructor of one.

So the completion of the destructor of one is sequenced-before the completion of the destructor of two, which explains what you are seeing.

How to delay destruction of static object (logger) in C++?

This is trivial.

First you don't use global static objects ( you should not be using global state like that). You use function static objects so you can control the order of creation/destruction.

So change this:

std::unique_ptr<ILogger> LoggerWrapper::logger_;
std::unique_ptr<Foo> foo;

Into:

 class GlobalLogger
{
public:
ILogger& getLogger() {
static ILogger logger; // if you must use unique_ptr you can do that here
return logger; // But much simpler to use a normal object.
}
};
class GlobalFoo
{
public:
Foo& getFoo() {
// If there is a chance that foo is going to
// use global logger in its destructor
// then it should simply call `GlobalLogger::getLogger()`
// in the constructor of Foo. You then
// guarantee the order of creation and thus destruction.
// Alternatively, you can call it here in thus
// function just before the declaration of foo.
static Foo foo;
return foo;
}
};

// Where you were using `logger_` use `GlobalLogger::getLogger()`
// Where you were using `foo` use `GlobalFoo::getFoo()`

If we use your original code as the starting point we can do this:

#include <iostream>
#include <memory>
#include <string>

// Please don't do this.
// This is the number one worst practice.
// https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice
using namespace std;

class ILogger {
public:
ILogger() {
std::cout << "ILogger CTOR" << std::endl;
}
~ILogger() {
std::cout << "ILogger DTOR" << std::endl;
}

virtual void OnLogEvent(const std::string& log_message) {
std::cout << "OnLogEvent: " << log_message << std::endl;
}
};

class LoggerWrapper
{
// Here store the logger
// as a static member of this private function.
// The the SetLogger() Log() functions get this reference.
static std::unique_ptr<ILogger>& getLogReference() {
static std::unique_ptr<ILogger> logger;
return logger;
}

public:
static void SetLogger(std::unique_ptr<ILogger> new_logger) {
// Save the new reference.
getLogReference() = std::move(new_logger);
}

// Use the logger if it has been set.
static void Log(const std::string& message) {
std::unique_ptr<ILogger>& logger_ = getLogReference();
if (!logger_) {
// cannot log
} else {
logger_->OnLogEvent(message);
}
};
};

class Foo {
public:
Foo(const std::string& name) : name_{name} {
// This calls Log()
// Which calls getLogReference()
// Which forces the creation of the function static
// variable logger so it is created before this
// object is fully initialized (if it has not already
// been created).
//
// This means this object was created after the logger
LoggerWrapper::Log(name_ + ": CTOR");
}
~Foo() {
// Because the Log() function was called in the
// constructor we know the loger was fully constructed first
// thus this object will be destroyed first
// so the logger object is guaranteed to be
// available in this objects destructor
// so it is safe to use.
LoggerWrapper::Log(name_ + ": DTOR");
}
private:
std::string name_;
};


std::unique_ptr<Foo>& globalFoo() {
// foo may destroy an object created later
// that has a destructor that calls LoggerWrapper::Log()
// So we need to call the Log function here before foo
// is created.
LoggerWrapper::Log("Initializing Global foo");
// Note: Unless somebody else has explicitly called SetLogger()
// the above line is unlikely to log anything as the logger
// will be null at this point.

static std::unique_ptr<Foo> foo;
return foo;
}

int main()
{
LoggerWrapper::SetLogger(std::make_unique<ILogger>());
globalFoo() = std::make_unique<Foo>("Global FOO");

// local variables do NOT get destroyed when calling exit!
auto foo_local = Foo("Local FOO");

exit(1);
}

Why is the order of destruction same as order of construction, with static object (C++)?

Static local variables will be destroyed at program exit.

The destructor for a block-scope static variable is called at program exit, but only if the initialization took place successfully.

So b will be destoryed firstly at the end of main(), a will be destroyed after that.

For the initialization,

are initialized the first time control passes through their declaration

So b will be initialized firstly in main(), then a gets initialized.

Destruction Order of Meyers Singletons

The point of this construct is to impose a construction order (and thus a destruction order).

Construction

Since these are local static variables, the order of construction is determined by the order in which their respective Instance functions are called for the first time.

Since that is done in main, the construction order is fully specified.

The only way to make the order unspecified is if you use them in static initialisation in different translation units, for instance if one has

C& the_c = C::Instance();

and the other has

D& the_d = D::Instance();

Destruction

The destruction of objects with static storage is the reverse of the order of construction.

3.6.3, Termination, paragraph 1:

If the completion of the constructor or dynamic initialization of an
object with static storage duration is sequenced before that of
another, the completion of the destructor of the second is sequenced
before the initiation of the destructor of the first.

So the destruction order is fully specified by the construction order.

Note that this singleton construct is well specified even if one of them depends on the other, regardless of translation unit.

That is, this is perfectly safe, and it doesn't matter where it's defined:

class C {
public:
static C& Instance() {
static C c(D::Instance());
return c;
}

~C(){ m_d.doSomething(); } // Yes, this is safe.
private:
C(D& d) : m_d(d) { m_d.doSomething(); } // Yes, this is safe.
D& m_d;
};

Destruction order in C++

a and b are of automatic duration, destroyed when the block ends. c is of static duration, destroyed when the program terminates. LIFO order only applies to objects destroyed at the same point in the program.



Related Topics



Leave a reply



Submit