[英]Controlling the order of a C++ destructor and that of the object's ivars
[英]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_A
和get_B
來獲取全局實例。 B
使用A
的部分應該使用get_A
,而使用B
應該使用get_B
。 請注意, get_B
在您的情況下是可選的。
B首次創建時會發生什么? (全局或函數中)構造函數將調用get_A
, 這將是創建A
的位置。 這讓你控制事物的構造順序。
注意我想我顛倒了你的A和B.
一般來說,沒有這樣的方法。 但是有一些解決方法。 通過使用全局指針並在main / WinMain中初始化/破壞它,可以獲得具有全局范圍和略小於全局生命周期的對象。 此外,您將全局狀態放在最后一個引用計數的堆對象中。
另外,考慮重新設計:)
“現代C ++設計”一書很好地涵蓋了這個問題。
Google圖書包含大部分掃描內容 - 請參閱第6.5節(第135頁) - 鏈接 。
您可以通過將指針放在全局空間中的對象上,然后在主體中按所需順序對其進行處理,並在主體末尾以所需順序銷毀它們,從而干凈利落地處理這個問題。
如果你使用懶惰的單例(返回按需創建的靜態),你可能最終會有一個單例在已經被刪除后使用另一個單例的可能性。 例如,假設您有一個全局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
,則可能仍會失敗並產生意外的輸出。 或者如果你想擁有多個這樣的“超級”全局變量(例如Log
和Config
),它們以隨機順序互相使用,你仍然會遇到這些問題。 在這種情況下,有時候最好在堆上分配一次,永遠不要刪除其中的一些對象。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.