[英]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.