简体   繁体   English

C ++控制全局对象的析构函数顺序

[英]C++ Controlling destructor order for global objects

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)在其构造函数和析构函数中访问(间接通过静态方法)另一个类(B)中的静态变量(STL容器)。

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. 对象可以是全局的,全局常量,另一个类的静态成员,存储在其他类(可能本身具有全局或静态实例)中,或者基本上可以是c ++对象的任何其他位置。

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 at some point (usually an access violation). 如果A对象在B中的静态成员之前构造或在B中的静态成员之后被破坏,则会在某些时候导致崩溃(通常是访问冲突)。

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? 是否有某种方法可以保证A类的所有实例(除了已经泄漏的那些实例,因为根据定义“丢失”并且因此不会被破坏)在B的静态变量之后被构造并被破坏?

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. 在输入main之前构造对象的顺序是未指定的。 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. 您使用get_Aget_B来获取全局实例。 The part where B uses A should use get_A , and your use of B should be with get_B . B使用A的部分应该使用get_A ,而使用B应该使用get_B Note the get_B is optional in your case. 请注意, get_B在您的情况下是可选的。

What happens when B is first created? B首次创建时会发生什么? (Either globally or in the function) The constructor will call get_A and that's where A will be created. (全局或函数中)构造函数将调用get_A将是创建A 位置。 This let's you control the order things get constructed. 这让你控制事物的构造顺序。

Note I think I reversed your A and B. 注意我想我颠倒了你的A和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. 通过使用全局指针并在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. “现代C ++设计”一书很好地涵盖了这个问题。

Google Books contains scans of much of it - see section 6.5 (page 135) - link . Google图书包含大部分扫描内容 - 请参阅第6.5节(第135页) - 链接

您可以通过将指针放在全局空间中的对象上,然后在主体中按所需顺序对其进行处理,并在主体末尾以所需顺序销毁它们,从而干净利落地处理这个问题。

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. 但是,您应该能够通过应用一些设计来解决您的问题,因此您可以在构建A和B的对象时(以及如何)获得一定程度的控制。 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. 看一下设计模式,如创作模式Singleton,它被认为是许多(如果不是大多数)案例的反模式 ,尽管值得了解它。 Also look at to Monostate pattern which may be used as a bit better Singleton. 另请参阅Monostate模式,它可以用作更好的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. 例如,假设您有一个全局HttpClient单例,允许您发出http请求。 Also, you probably want to have logging, which might be provided by Log singleton: 此外,您可能希望记录日志,这可能由Log singleton提供:

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

Same for Log singleton. Log singleton也是如此。 Now, imagine HttpClient constructor and destructor simply log that that HttpClient was created and deleted. 现在,假设HttpClient构造函数和析构函数只是记录HttpClient创建和删除。 In this case destructor of HttpClient may end up using deleted Log singleton. 在这种情况下, HttpClient析构函数可能最终使用已删除的Log单例。

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. 到目前为止一切都是正确的,只是因为它发生在HttpClient之前构建了Log ,这意味着HttpClient仍然可以在其析构函数中使用Log Now simply comment out logging code in HttpClient constructor and you'll end up with this output : 现在只需在HttpClient构造函数中注释掉日志代码,你就会得到这个输出

Log
HttpClient::request
~Log
~HttpClient

As you can see, log is being used after its destructor ~Log was already called. 如您所见,日志在其析构函数~Log已被调用之后被使用。 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 . 现在,无论构造这些单例的顺序如何,销毁顺序将确保在HttpClient之后Log被破坏。 This, however, might still fail and produce unexpected output if HttpClient was used from a global static constructor. 但是,如果从全局静态构造函数中使用HttpClient ,则可能仍会失败并产生意外的输出。 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. 或者如果你想拥有多个这样的“超级”全局变量(例如LogConfig ),它们以随机顺序互相使用,你仍然会遇到这些问题。 In such cases sometimes it's just better allocate once on heap never delete some of these objects. 在这种情况下,有时候最好在堆上分配一次,永远不要删除其中的一些对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM