2012-11-14 21 views
2

如果我在項目中有兩個源文件,每個文件都定義了一個具有相同名稱的類,那麼決定使用哪個類的哪個版本?什麼決定哪個類定義包含在兩個源文件中的同名命名類中?

例如:

// file1.cpp: 

#include <iostream> 
#include "file2.h" 

struct A 
{ 
    A() : a(1) {} 
    int a; 
}; 

int main() 
{ 
    // foo() <-- uncomment this line to draw in file2.cpp's use of class A 

    A a; // <-- Which version of class A is chosen by the linker? 
    std::cout << a.a << std::endl; // <-- Is "1" or "2" output? 
} 

...

//file2.h: 

void foo(); 

...

// file2.cpp: 

#include <iostream> 
#include "file2.h" 

struct A 
{ 
    A() : a(2) {} 
    int a; 
}; 

void foo() 
{ 
    A a; // <-- Which version of class A is chosen by the linker? 
    std::cout << a.a << std::endl; // <-- Is "1" or "2" output? 
} 

我已經能夠得到A不同版本的鏈接器選擇,使用相同的代碼 - 僅僅通過改變我輸入代碼的順序(沿途建立)。

當然,在相同名稱空間中使用相同名稱包含不同的類定義是一種糟糕的編程習慣。但是,是否有定義規則來確定鏈接器將選擇哪個類 - 如果是,它們是什麼?作爲這個問題的一個有用的附錄,我想知道(一般情況下)編譯器/鏈接器如何處理類 - 編譯器,當它構建每個源文件時,將類名和編譯類定義合併到目標文件,而鏈接器(在名稱衝突的場景中)拋出了一組編譯的類函數/成員定義?

名稱衝突的問題並非神祕 - 我現在意識到它每次都會發生一個只有頭文件的模板文件,它由兩個或多個源文件構成(隨後相同的模板類被實例化,並且相同的成員在這些多個源文件中稱爲函數),就像STL的一個常見場景一樣。每個源文件都必須具有相同的實例化模板類函數的單獨編譯版本,因此鏈接器必須在鏈接時間在這些函數的不同編譯版本中進行選擇),我想。

- 增編有關於Java相關的問題 -

我注意到,各種各樣的回答都表示對C的一個定義規則(http://en.wikipedia.org/wiki/One_definition_rule)++。作爲一個有趣的說法,我是否正確地認爲Java沒有這樣的規則 - 所以Java規範允許在Java中允許多個不同的定義?

回答

2

如果一個C++程序提供了同一個類的兩個定義(即在同一個名稱空間內並且名字相同),那麼程序違反了標準的規則,你會得到未定義的行爲。究竟發生了什麼取決於編譯器和鏈接器:有時你會得到一個鏈接器錯誤,但這不是必需的。

明顯的修復方法是不會有衝突的類名稱。獲得獨特的類名的最簡單的方法是一個未命名的命名空間中定義了本地使用的類型:

// file1.cpp 
namespace { 
    class A { /*...*/ }; 
} 

// file2.cpp 
namespace { 
    class A { /*...*/ }; 
} 

這兩個類不會發生衝突。

+0

這個標準事實上是否表示一個程序不允許提供同一類的不同定義?這讓我覺得這個標準不能說明這一點,因爲這是不可能實現的 - 例如,如果以後修改了兩個源文件中的一個並重建了程序,而是之前構建的第二個源文件的編譯目標文件與之前版本的編譯器相關聯。因此,標準中可能沒有規定禁止使用不同的類別定義?這似乎是一個困難的灰色地帶。 –

+0

@DanNissenbaum:沒有歧義!該標準明確規定了一個定義規則:3.2 [basic.def.odr]第6段。然而,在評論中引用太長。這個定義的重點在於編譯器和/或鏈接器可能無法檢查約束是否被遵守。因此,該程序具有未定義的行爲。 –

3

這樣的程序違反了One Definition Rule並且顯示未定義的行爲。

如果程序中有多個類或內聯函數的定義(在不同的翻譯單元或源文件中),則所有定義必須相同。無論是編譯器還是鏈接器都不需要診斷此規則的所有違規行爲(並且並非所有違規行爲都可以輕鬆診斷)。

2

這只是成功鏈接,因爲2個構造函數的定義暗示爲inline。嘗試在課下移動它們,而不要使用inline關鍵字。您濫用的鏈接類型會告訴鏈接器將會出現多個定義,通常情況下,您打破了「一個定義規則」(您實際上正在破壞)的錯誤。通常情況下,這種情況允許你看似破壞ODR,像模板一樣,它總是在不同的翻譯單元中有多個相同的定義。但是,這是一個條件:不同翻譯單位的定義必須相同。

最終它取決於您的編譯器在您的示例中使用。

1

如果你允許它(你應該),編譯器會給你多個定義的警告。

gnu鏈接器按照您在命令行上呈現文件的順序解析了符號,因此它使用它看到的第一個定義。不確定所有連接器是否以相同的方式工作。

1

One Definition Rule存在的原因是,使用哪個定義並不重要,它們都是相同的。完全取決於使用哪個版本的編譯器和鏈接器,或者它們是否一致。唯一可從外部看到的副作用是當函數內部存在靜態變量時,必須在函數的所有實例之間使用變量的單個實例。

通過違反One Definition Rule,您將以與正確編寫的程序無關的方式暴露編譯器/鏈接器的機制。

相關問題