开发者

C++ Controlling destructor order for global objects

开发者 https://www.devze.com 2022-12-18 07:39 出处:网络
I\'ve got a class (A) that accesses (indirectly via a static method) a static variable (an STL container) in another class (B) in its constructor and destructor.

I've got a class (A) that accesses (indirectly via a static method) a static variable (an STL container) in another class (B) in its constructor and destructor.

A objects may be global, global constants, static members of another class, stored in other classes (which may themselves have global or static instances) or basically anywhere else a c++ object can be.

If an A object is constructed before the static members in B or destructed after the static members in B, it will cause a crash a开发者_如何学Ct some point (usually an access violation).

Is there some way to guarantee that all instances of class A (except those that have leaked, since by definition there "lost" and so wont be destructed any way) are constructed after and destructed before B's static variable?

I've seen some solutions for making a specific variable be constructed/destructed before/after another, however not a general case of all instances of a given type so am not sure how to approach this.


No. This is known as the static-initialization fiasco. The order that objects get constructed prior to entering main is unspecified. The only guarantee is that it happens.

What you can do is lazy-initialize. This means your objects won't be initialized until you use them. Such as:

struct A { /* some data */ };
struct B { B(void){ /* get A's data */ } };

A& get_A(void)
{
    static A instance;
    return instance;
}

B& get_B(void)
{
    static B instance;
    return instance;
}

You use get_A and get_B to get the global instances. The part where B uses A should use get_A, and your use of B should be with get_B. Note the get_B is optional in your case.

What happens when B is first created? (Either globally or in the function) The constructor will call get_A and that's where A will be created. This let's you control the order things get constructed.

Note I think I reversed your A and B.


In general, no such method. There are workarounds, though. You can get an object with global scope and a slightly-less-than global lifetime by having a global pointer and initializing/destructing it in main/WinMain. Also, you place your global state to be destructed last in a ref-counted heap object.

Also, consider redesign :)


The book "Modern C++ Design" covers this issue nicely.

Google Books contains scans of much of it - see section 6.5 (page 135) - link.


You can handle this cleanly, by putting pointers to the objects in global space, then newing them in the desired order in your main, and destroying them in the desired order at the end of the main.


As others pointed, there is no standard and portable way to solve this problem, due to the Static initialization order fiasco issue.

However, you should be able to solve your problem by applying a bit of design, so you gain a degree of control when (and how) objects of A and B are constructed. Take a look on design patterns like creational pattern Singleton it is considered in many (if not most) cases as anti-pattern, despite it is worth to learn about it. Also look at to Monostate pattern which may be used as a bit better Singleton. These patterns can help to control object creation and lifetime, so things are properly initialized before use.

Generally, it's a good idea to avoid globals - sticking to deglobalisation is a good idea.


In case if you use lazy singletons (that return on-demand created statics) you may end up with possibility where one singleton uses another one after it was already deleted. For example, imagine you have a global HttpClient singleton that allows you to make http requests. Also, you probably want to have logging, which might be provided by Log singleton:

class HttpClient
{
    ...
    static HttpClient& singleton()
    {
        static HttpClient http;
        return http;
    }
};

Same for Log singleton. Now, imagine HttpClient constructor and destructor simply log that that HttpClient was created and deleted. In this case destructor of HttpClient may end up using deleted Log singleton.

Sample code:

#include <stdio.h>

class Log
{
    Log()
    {
        msg("Log");
    }

    ~Log()
    {
        msg("~Log");
    }

public:    
    static Log& singleton()
    {
        static Log log;
        return log;
    }

    void msg(const char* str)
    {
        puts(str);
    }
};

class HttpClient
{
    HttpClient()
    {
        Log::singleton().msg("HttpClient");
    }
    ~HttpClient()
    {
        Log::singleton().msg("~HttpClient");
    }

public:
    static HttpClient& singleton()
    {
        static HttpClient http;
        return http;
    }
    void request()
    {
        Log::singleton().msg("HttpClient::request");
    }
};

int main()
{
    HttpClient::singleton().request();
}

and the output is:

 Log
 HttpClient
 HttpClient::request
 ~HttpClient
 ~Log

So far everything is correct, simply because it happened that Log was constructed before HttpClient, which means that HttpClient can still use Log in its destructor. Now simply comment out logging code in HttpClient constructor and you'll end up with this output:

Log
HttpClient::request
~Log
~HttpClient

As you can see, log is being used after its destructor ~Log was already called. As pointed out, deglobalization might be a better approach, but if you want to use on-demand created singletons and make some of them live longer than the others, then you can make such singletons use a global static initialized on demand. I use that approach quite often in production code:

class Log
{
    friend std::unique_ptr<Log>::deleter_type;
    ...
    static std::unique_ptr<Log> log;

    static Log& createSingleton()
    {
        assert(!log);
        log.reset(new Log);
        return *log;
    }

public:    
    static Log& singleton()
    {
        static Log& log = createSingleton();
        return log;
    }
};

std::unique_ptr<Log> Log::log;

Now, regardless of order in which these singletons were constructed, destruction order will ensure that Log is destructed after HttpClient. This, however, might still fail and produce unexpected output if HttpClient was used from a global static constructor. Or if you want to have multiple such "super" globals (like Log and Config for example) that use each other in random order, you will still have these problem. In such cases sometimes it's just better allocate once on heap never delete some of these objects.

0

精彩评论

暂无评论...
验证码 换一张
取 消