2017-10-12 68 views
2

前一段時間,我用一組嵌套類編寫了一些代碼。今天看看它,我想知道爲什麼它編譯時,我實例化類型的對象C.這段代碼爲什麼編譯時不會抱怨構造函數?

我的困惑是這樣的。 B有一個私人構造函數。這個構造函數的實現關心A的構造,但是A的構造需要B的一個實例。我覺得它是一個雞和雞蛋的場景。 B的建設需要建造A,這需要建造B等等。

我有以下的類 - 剝離右後衛演示問題:

// ******* A ******** // 
class A { 
public: 
    A(A& _Parent, int id); 
private: 
    A& Parent; 
}; 

inline A::A(A& _Parent, int id) 
: Parent(_Parent) 
{ 
} 


// ******* B ******** // 
class B:public A { 
public: 
    static B& GetInstance(); 
private: 
    B(); 
}; 

inline B::B() 
: A(B::GetInstance(), 0) 
{ 
} 

inline B& B::GetInstance() 
{ 
    static B b; 
    return b; 
} 


// ******* C ******** // 
class C:public A { 
public: 
    C(); 
}; 

inline C::C() 
: A(B::GetInstance(), 0) 
{ 
} 
+0

是的。我認爲這是不確定的行爲,但像大多數編譯器實現靜態函數/內部範圍對象的建設,這滑過裂縫,實際工作... –

+0

注:也許這只是你的樣品中存在的問題,而不是真實的代碼;但是會導致未定義行爲將靜態對象放入內聯函數中(因爲靜態對象對於不同的翻譯單元將不同,但內聯函數對於所有單元必須相同)。希望在真正的代碼中,'B :: GetInstance()'沒有在頭文件中實現。作爲一個經驗法則,儘量避免提及任何靜態對象的內聯函數 –

+0

有了'gcc'這也引發運行時異常[__gnu_cxx :: recursive_init_error(https://gcc.gnu.org/onlinedocs/gcc- 4.6.3 /的libstdC++/API/a00064.html)。所以即使這個問題沒有在編譯時被捕獲,初始化也會失敗。 – Jonesinator

回答

2

的問題縮小了:

inline B& B::GetInstance() 
{ 
    static B b; 
    return b; 
} 

inline B::B() 
: A(B::GetInstance(), 0) 
{ 
} 

static B b;創建B對象第一次函數叫做。然而,b的建設,然後呼籲GetInstance,其中達到線static B b;b仍在建設中。

這種情況由C++ 14 [stmt.dcl]/4覆蓋:

[...]這樣的變量被認爲是它的初始化完成時初始化。 [...]如果控制在初始化變量時遞歸地重新輸入聲明,則的行爲未定義

我刪除了一部分,討論如果引發異常會發生什麼,或者如果兩個不同的線程同時嘗試初始化靜態變量。該標準確實允許本地靜態變量在函數第一次調用之前被初始化,但即使實現這樣做,同樣的問題會出現遞歸控制重新輸入聲明的問題。


由於未定義的行爲,任何事情都可能發生。一個可能的結果是它似乎按預期工作。該標準並不要求在編譯時進行診斷 - 這是一個難以分析的問題。如註釋中所示,一個版本的gcc在運行時檢測到此異常並引發異常。也許你的原始編譯器通過有一個標誌來指示執行是否已經到達那條線,並在調用構造函數之前設置標誌來實現本地靜態。

標準中存在未定義行爲的基本原理是避免在實現實現定義良好的行爲(在這種情況下,確保本地靜態只初始化一次的方法)上設置約束。

當然,您應該找到解決問題的方法,因爲未定義的行爲在將來可能以不同的方式表現出來,而且難以預測。

1

雖然您沒有顯示任何此類代碼,但讓我們假設某些嘗試調用C的默認構造函數或以其他方式調用B::GetInstance()

GetInstance()的第一條語句是static B b;。由於這是我們第一次到達此處,因此需要通過調用默認構造函數B::B()來初始化對象b

B::B()所做的第一件事情是致電GetInstance(),然後打算將結果傳遞給構造函數A::A(A&, int)

這在聲明static B b;再次把我們帶回。並且C++標準規定:([stmt.dcl]/4):

動態初始化靜態存儲持續時間([basic.stc.static])或線程存儲持續時間([basic。 stc.thread])是在控件第一次通過其聲明時執行的;這樣的變量在初始化完成時被認爲是初始化的。如果控制器在變量被初始化時遞歸地輸入聲明,則行爲是不確定的。

未定義行爲意味着什麼事情都可能發生。它看起來可能工作,它可能會崩潰或掛起您的程序,或者它可能會初始化不正確並繼續。

相關問題