1. 了解變量析構(gòu)順序的必要性
??大多數(shù)時間里,我們更關(guān)注的是變量的初始化順序,因為我們需要確保使用到的變量都是初始化好的變量。然而,當(dāng)項目變大、變復(fù)雜的時候,我們可能就會開始考慮程序的退出問題,尤其是多線程程序的退出,變量的析構(gòu)順序可能會影響到程序是否能優(yōu)雅、安全地退出。
2. 全局(靜態(tài))變量的析構(gòu)順序
??我們都知道C++規(guī)范規(guī)定,變量析構(gòu)的順序和構(gòu)造的順序是相反的。對于在同一個編譯單元內(nèi)的全局(靜態(tài))變量的初始化順序,與他們聲明的順序是相同的,而析構(gòu)的順序則與初始化的順序是相反的;對于不在同一個編譯單元的全局(靜態(tài))變量的初始化順序是不確定的。
3. 局部靜態(tài)變量的析構(gòu)順序
??相對復(fù)雜的是局部靜態(tài)變量的初始化與析構(gòu)流程。對于局部靜態(tài)變量,大家都知道的一點就是初始化是發(fā)生在函數(shù)第一次運行的時候,所以我們可以推導(dǎo)出,局部靜態(tài)變量的初始化肯定是晚于全局(靜態(tài))變量的,所以其析構(gòu)肯定是早于全局(靜態(tài))變量的。
4. 析構(gòu)順序的分析
??前面我們直接給出了結(jié)論,下面用地段代碼來看一下局部靜態(tài)變量晚于全局(靜態(tài))變量析構(gòu)的原因:
#include <stdio.h>
#include <string>
#include <string.h>
class A
{
public:
A()
{
}
A(const std::string &name)
{
name_ = name;
printf("A of %s\n", name_.c_str());
}
~A()
{
printf("~A of %s\n", name_.c_str());
}
private:
std::string name_;
};
A a("global a");
void test()
{
static A local_a("local a");
}
int main()
{
test();
return 0;
}
然后我們編譯成匯編語言,會看到,當(dāng)程序定義一個結(jié)構(gòu)體變量時,會在定義結(jié)束后調(diào)用 __cxa_atexit,來注冊程序exit時調(diào)用的析構(gòu)函數(shù)。全局(靜態(tài))變量位于代碼段,構(gòu)造會在進(jìn)入main函數(shù)之前,對于局部靜態(tài)變量,構(gòu)造函數(shù)會在main函數(shù)調(diào)用func時,注冊晚于全局變量,所以析構(gòu)的調(diào)用就會早于全局變量的析構(gòu)函數(shù)。
如果想要控制一個局部靜態(tài)變量的析構(gòu),晚于一個全局(靜態(tài))變量,則可以將對函數(shù)的調(diào)用,放到全局(靜態(tài))變量的構(gòu)造函數(shù)內(nèi)調(diào)用
#include <stdio.h>
#include <string>
#include <string.h>
void func();
class A
{
public:
A()
{
}
A(const std::string &name)
{
name_ = name;
printf("A of %s\n", name_.c_str());
func();
}
~A()
{
printf("~A of %s\n", name_.c_str());
}
private:
std::string name_;
};
class B
{
public:
B()
{
}
B(const std::string &name)
{
name_ = name;
printf("B of %s\n", name_.c_str());
}
~B()
{
printf("~B of %s\n", name_.c_str());
}
private:
std::string name_;
};
A a("global a");
void func()
{
static B lb("local b");
}
int main()
{
return 0;
}
最后補充一個結(jié)論,析構(gòu)函數(shù)的順序:
局部靜態(tài)變量 ==> attribute((destructor)) ==> 全局變量