簡體   English   中英

C ++中靜態對象的銷毀順序

[英]Destruction order of static objects in C++

我可以控制銷毀靜態對象的順序嗎? 有什么方法可以執行我想要的命令? 例如,以某種方式指定我希望某個對象最后被破壞,或者至少在另一個靜態對象之后被破壞?

靜態對象以相反的順序破壞。 而且施工順序很難控制。 您唯一可以確定的是,將按定義順序構造在同一編譯單元中定義的兩個對象。 其他任何事物或多或少都是隨機的。

對此的其他答案堅持認為它無法完成。 他們是對的,依據規范-但一個小竅門,可以讓你做到這一點。

創建只有一個靜態變量,它包含了所有其他的事情,你通常會做靜態變量,像這樣一類或結構的,:

class StaticVariables {
    public:
    StaticVariables(): pvar1(new Var1Type), pvar2(new Var2Type) { };
    ~StaticVariables();

    Var1Type *pvar1;
    Var2Type *pvar2;
};

static StaticVariables svars;

您可以創建你需要的任何順序變量,更重要的是, 摧毀他們在任何你需要的才能,在構造函數和析構函數StaticVariables 為了使其完全透明,您也可以創建對變量的靜態引用,如下所示:

static Var1Type &var1(*svars.var1);

Voilà-完全控制。 :-)也就是說,這是額外的工作,通常是不必要的。 但是,當必要時,了解它非常有用。

靜態對象的破壞順序與生成它們的順序相反(例如,第一個破壞的對象最后被破壞),您可以使用第47條中所述的技術來控制靜態對象的生成順序,在Meyers的書《 Effective C ++ 》中“ 確保在使用全局對象之前對其進行了初始化 ”。

例如,以某種方式指定我希望某個對象最后被破壞,或者至少在另一個靜態注入之后被破壞?

確保在其他靜態對象之前構造它。

如何控制施工順序? 並非所有的靜態函數都在同一個dll中。

為了簡單起見,我將忽略它們不在同一個DLL中的事實。

我對邁耶斯項目47(長4頁)的解釋如下。 假設您在這樣的頭文件中定義了全局變量...

//GlobalA.h
extern GlobalA globalA; //declare a global

...向這樣的包含文件添加一些代碼...

//GlobalA.h
extern GlobalA globalA; //declare a global
class InitA
{
  static int refCount;
public:
  InitA();
  ~InitA();
};
static InitA initA;

這樣做的結果是,任何包含GlobalA.h的文件(例如,定義了第二個全局變量的GlobalB.cpp源文件)都將定義InitA類的靜態實例,該實例將在該實例中的其他任何對象之前進行構造源文件(例如,在第二個全局變量之前)。

該InitA類具有靜態引用計數器。 當構造第一個InitA實例時(現在可以保證在構造GlobalB實例之前),InitA構造函數可以執行其必須做的一切以確保初始化globalA實例。

簡短的回答:一般而言,不會。

稍長的答案:對於單個轉換單元中的全局靜態對象,初始化順序從上到下,銷毀順序恰好相反。 多個翻譯單元之間的順序是不確定的。

如果您確實需要特定的訂單,則需要自己進行調整。

在標准C ++中無法做到這一點,但是如果您對特定的編譯器內部有很好的了解,則可以實現。

在Visual C ++中,指向靜態init函數的指針位於.CRT$XI段(對於C類型的靜態init)或.CRT$XC段(對於C ++類型的靜態init)。鏈接器收集所有聲明並將它們按字母順序合並。 通過使用以下方法在適當的段中聲明對象,可以控制靜態初始化發生的順序:

#pragma init_seg

例如,如果要在文件B之前創建文件A的對象:

文件A.cpp:

#pragma init_seg(".CRT$XCB")
class A{}A;

文件B.cpp:

#pragma init_seg(".CRT$XCC")
class B{}B;

.CRT$XCB.CRT$XCC之前合並。 當CRT遍歷靜態init函數指針時,它將在文件B之前遇到文件A。

在Watcom中,該段是XI,#pragma initialize的變體可以控制構造:

#pragma initialize before library
#pragma initialize after library
#pragma initialize before user

...有關更多信息,請參見文檔

您真的需要在main之前初始化變量嗎?

如果您不這樣做,則可以使用簡單的習慣用法輕松地實際控制構造和破壞的順序,請參見此處:

#include <cassert>

class single {
    static single* instance;

public:
    static single& get_instance() {
        assert(instance != 0);
        return *instance;
    }

    single()
    // :  normal constructor here
    {
        assert(instance == 0);
        instance = this;
    }

    ~single() {
        // normal destructor here
        instance = 0;
    }
};
single* single::instance = 0;

int real_main(int argc, char** argv) {
    //real program here...

    //everywhere you need
    single::get_instance();
    return 0;
}

int main(int argc, char** argv) {
    single a;
    // other classes made with the same pattern
    // since they are auto variables the order of construction
    // and destruction is well defined.
    return real_main(argc, argv);
}

它並不能阻止您實際嘗試創建該類的第二個實例,但是如果您這樣做,則斷言將失敗。 以我的經驗,它可以正常工作。

可以通過具有有效地實現類似的功能static std::optional<T>代替T 只需像對變量一樣進行初始化,與間接調用一起使用,並通過分配std::nullopt (或者對於boost,使用boost::none )銷毀它std::nullopt

它與具有指針的不同之處在於它具有預分配的內存,我想這就是您想要的。 因此,如果銷毀它並(可能以后再重新創建)它,則對象將具有相同的地址(可以保留),並且此時您無需支付動態分配/取消分配的費用。

如果您沒有std:: / std::experimental::請使用boost::optional<T>

不,你不能。 您永遠不應依賴於靜態對象的構造/破壞。

您始終可以使用單例來控制構建/銷毀全局資源的順序。

暫無
暫無

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

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