2016-09-16 112 views
4

關於在C++中銷燬靜態變量的順序,是否有任何有關靜態對象相對於其靜態成員變量的生命週期的保證?在C++中,靜態對象是否可以超出其靜態成員變量?

舉例來說,如果我有這樣的事情(瘋狂簡化的例子僅用於演示目的):

class Object { 
    static std::vector< Object * > all_objects; 

public 
    Object() { 
     all_objects.push_back(this); 
    } 

    ~Object() { 
     all_objects.erase(
      std::remove(all_objects.begin(), all_objects.end(), this), 
      all_objects.end()); 
    } 
}; 

這將是「安全的」關於在不同的編譯單元的靜態對象?也就是說,是否有任何保證all_objects成員變量將至少與任何有效對象一樣長,或者在最後一個對象實例之前可能會銷燬all_objects

並會在被用作庫中的代碼的答案變化(Python中說),而不是與自己的主要一個獨立的程序()?

回答

1

這將是「安全的」關於在不同的編譯單元的靜態對象?

這不是在初始化時的安全。不能保證all_objects將在構建編譯單元中的static對象時被初始化。

我不清楚終止的順序。我的猜測是破壞是以相反的順序進行的。如果構建/初始化不安全,破壞也可能是不安全的。

的一種方式,使其安全在初始化時是一個功能包all_objects

class Object { 
    static std::vector<Object *>& get_all_objects(); 

public 
    Object() { 
     get_all_objects().push_back(this); 
    } 

    ~Object() { 
     std::vector<Object *>& all_objects = get_all_objects(); 
     all_objects.erase(
      std::remove(all_objects.begin(), all_objects.end(), this), 
      all_objects.end()); 
    } 
}; 

std::vector<Object *>& Object::get_all_objects() 
{ 
    static std::vector<Object *> all_objects; 
    return all_objects; 
} 

這就是C++ 11 Standard(3.6。3/1)不得不說關於具有靜態存儲持續時間的物體的破壞。

如果構造或具有靜態存儲持續時間的對象的動態初始化的完成之前的另一個測序,第二的析構函數完成第一的析構函數開始前進行測序。

鑑於上述方法是安全的銷燬。 all_objects只會在最後的Object被銷燬後纔會被銷燬。

+0

函數級靜態變量與全局(或類)級靜態變量的破壞順序規則是什麼?我們確定'all_objects'會堅持到所有的靜態對象消失嗎? –

+0

@ R.M。,請參閱最新的答案。 –

+0

「構造函數的完成」是否改變了一些東西?例如http://stackoverflow.com/a/335746/3022952表明,當您在Object的構造函數中調用get_all_objects()函數時,all_objects變量將在完成第一個Object的構造函數之前完成其構造函數,這意味着all_objects的析構函數只會在所有*對象的析構函數完成後調用。或者我解釋錯了什麼? –

1

對於不同的編譯單元中的靜態Objects,這會是「安全」嗎?

不,它是不是安全。

這是一個當它不安全的例子,因爲靜態數據初始化的相對順序不能保證。有一些特定於平臺的方法可以實現這一點。

See the FAQ for techniques to work around this static initialisation fiasco。該技術基本上將靜態成員隱藏在一個函數中,然後在第一次使用時進行初始化。

只要添加到靜態成員的對象被適當管理,即不是靜態的,它們不會留在某個地方或處於某種未定義的狀態,並且不會遭受其他未定義的行爲,它可以變得安全。

如果代碼被用作庫(在Python中說)而不是作爲獨立的程序使用它自己的main(),那麼答案會改變嗎?

我不認爲這將由標準定義,而不是它被實現定義。據我所知,不,考慮到流行的實現,平臺和他們的ABI等,答案不會改變。

1

靜態變量真的具有全局作用域,因爲它們不在函數或方法的堆棧中。所以析構函數在最後一次被調用。

所以在單線程環境中,我看不到任何問題。這是一個愚蠢的例子,但它確實運行。在return語句之後,調用兩個靜態的析構函數。

ob.h 
class ob 
{ 
    static int a; 
    static int b; 
public: 
    ob() 
    { 
    a++; 
    } 
    ~ob() 
    { 
    b--; 
    } 
}; 

main.cpp #include ob.h;

int ob::a = 0; 
int ob::b = 0; 

void tt() 
{ 
    static ob zz; 
} 

int main() 
{ 
    static ob yy; 
    tt(); 

    { 
    ob b; 
    } 
    ob a; 

    return 1; 
} 

關於在另一個編譯單元的靜態增值經銷商,將取決於你如何使用它。例如,如果所有內容都被內聯並且頭文件在A.dll和B.dll中使用,則它們之間不會有任何引用,您必須初始化每個單元中的靜態,以便爲它們指定一個唯一地址。但是,如果它在一個lib或dll中,它將被導出,您將使用相同的內存地址。 在我們有同一個班級的兩個版本之前,我已經看到了這個問題。對象A是1.0,對象B是1.2。它們不是直接導出,而是用於導出的函數。爲對象調用了錯誤的析構函數。這是一個非常糟糕的編碼選擇,併發生了變化。 在多線程構建中,取決於您如何使用對象,它可能非常糟糕。你無法知道破壞的順序,你可以試圖在破壞後訪問某些東西。

總的來說我會說不是一個好的做法。它會工作,但在更復雜的情況下,未來的變化可能會讓事情變得更快。

0

要完成@Niall的答案,雖然初始化的順序是未定義的,但銷燬的順序將與初始化順序相反。

確保任何事情的唯一方法是通過將對象作爲靜態局部變量創建全局函數(如其他答案所示)。

在這種情況下,你就知道肯定了static對象將「之前的」類的靜態成員被刪除,因爲它創造了「後」(你第一次調用全局函數):

class Object { 
    static std::vector< Object * > all_objects; 

public 
    Object() { 
     all_objects.push_back(this); 
    } 

    ~Object() { 
     all_objects.erase(
      std::remove(all_objects.begin(), all_objects.end(), this), 
      all_objects.end()); 
    } 
}; 

Object& static_obj() 
{ 
    static Object obj; 
    return obj; 
} 

std::vector< Object * > Object::all_objects; // It is created first (before main) 

int main() 
{ 
    Object& o = static_obj(); // `obj` is initialized here. 
} // At the end of the program, `obj` will be destroid first.