簡體   English   中英

C ++控制全局對象的析構函數順序

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

我有一個類(A)在其構造函數和析構函數中訪問(間接通過靜態方法)另一個類(B)中的靜態變量(STL容器)。

對象可以是全局的,全局常量,另一個類的靜態成員,存儲在其他類(可能本身具有全局或靜態實例)中,或者基本上可以是c ++對象的任何其他位置。

如果A對象在B中的靜態成員之前構造或在B中的靜態成員之后被破壞,則會在某些時候導致崩潰(通常是訪問沖突)。

是否有某種方法可以保證A類的所有實例(除了已經泄漏的那些實例,因為根據定義“丟失”並且因此不會被破壞)在B的靜態變量之后被構造並被破壞?

我已經看到了一些解決方案,用於使特定變量在另一個之前/之后被構造/銷毀,但是不是給定類型的所有實例的一般情況,因此我不確定如何處理它。

不。這稱為靜態初始化慘敗 在輸入main之前構造對象的順序是未指定的。 唯一的保證就是它發生了。

你可以做的是懶惰初始化。 這意味着在您使用它們之前不會初始化您的對象。 如:

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;
}

您使用get_Aget_B來獲取全局實例。 B使用A的部分應該使用get_A ,而使用B應該使用get_B 請注意, get_B在您的情況下是可選的。

B首次創建時會發生什么? (全局或函數中)構造函數將調用get_A將是創建A 位置。 這讓你控制事物的構造順序。

注意我想我顛倒了你的A和B.

一般來說,沒有這樣的方法。 但是有一些解決方法。 通過使用全局指針並在main / WinMain中初始化/破壞它,可以獲得具有全局范圍和略小於全局生命周期的對象。 此外,您將全局狀態放在最后一個引用計數的堆對象中。

另外,考慮重新設計:)

“現代C ++設計”一書很好地涵蓋了這個問題。

Google圖書包含大部分掃描內容 - 請參閱第6.5節(第135頁) - 鏈接

您可以通過將指針放在全局空間中的對象上,然后在主體中按所需順序對其進行處理,並在主體末尾以所需順序銷毀它們,從而干凈利落地處理這個問題。

正如其他人指出的那樣,由於靜態初始化順序慘敗問題,沒有標准和可移植的方法來解決這個問題。

但是,您應該能夠通過應用一些設計來解決您的問題,因此您可以在構建A和B的對象時(以及如何)獲得一定程度的控制。 看一下設計模式,如創作模式Singleton,它被認為是許多(如果不是大多數)案例的反模式 ,盡管值得了解它。 另請參閱Monostate模式,它可以用作更好的Singleton。 這些模式可以幫助控制對象創建和生命周期,因此在使用之前可以正確初始化。

一般來說,避免全局是一個好主意 - 堅持去全球化是一個好主意。

如果你使用懶惰的單例(返回按需創建的靜態),你可能最終會有一個單例在已經被刪除后使用另一個單例的可能性。 例如,假設您有一個全局HttpClient單例,允許您發出http請求。 此外,您可能希望記錄日志,這可能由Log singleton提供:

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

Log singleton也是如此。 現在,假設HttpClient構造函數和析構函數只是記錄HttpClient創建和刪除。 在這種情況下, HttpClient析構函數可能最終使用已刪除的Log單例。

示例代碼

#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();
}

輸出是:

 Log
 HttpClient
 HttpClient::request
 ~HttpClient
 ~Log

到目前為止一切都是正確的,只是因為它發生在HttpClient之前構建了Log ,這意味着HttpClient仍然可以在其析構函數中使用Log 現在只需在HttpClient構造函數中注釋掉日志代碼,你就會得到這個輸出

Log
HttpClient::request
~Log
~HttpClient

如您所見,日志在其析構函數~Log已被調用之后被使用。 正如所指出的,去全球化可能是一種更好的方法,但是如果你想使用按需創建的單例並使它們中的一些比其他單體更長壽,那么你可以使這些單例使用按需初始化的全局靜態 我經常在生產代碼中使用這種方法:

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;

現在,無論構造這些單例的順序如何,銷毀順序將確保在HttpClient之后Log被破壞。 但是,如果從全局靜態構造函數中使用HttpClient ,則可能仍會失敗並產生意外的輸出。 或者如果你想擁有多個這樣的“超級”全局變量(例如LogConfig ),它們以隨機順序互相使用,你仍然會遇到這些問題。 在這種情況下,有時候最好在堆上分配一次,永遠不要刪除其中的一些對象。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM