2017-09-21 94 views
1

試想一個情況我有兩個不同的翻譯單元a.cpp衝突的模板定義和ODR

#include <iostream> 

double bar(); 

template <typename T> 
T foobar(T t) { 
    return t; 
} 

int main() { 
    std::cout << "foobar called from b.cpp: " << bar() << '\n'; 
    std::cout << "foobar called from a.cpp: " << foobar(1.) << '\n'; 
} 

和b.cpp:

template <typename T> 
T foobar(T t) { 
    return t + 1.; 
} 

double bar() { 
    return foobar(1.); 
} 

我知道,模板,也有例外ODR,也就是編譯器會標記實例化的函數模板,並且在鏈接過程中除了一個之外將會刪除所有的函數模板。我注意到編譯器實際上並不關心這些實例在不同翻譯單元中生成的代碼是否實際相同或至少是等價的。

在上面的代碼中,情況就是這樣。編譯時,鏈接和與

c++ a.cpp b.cpp -o result -std=c++17 && ./result 

運行它會產生的結果

foobar called from b.cpp: 1 
foobar called from a.cpp: 1 

因此很明顯,目標文件B.O裏面的實例得到了扔掉贊成一個從A.O。當編譯與b.cpp和a.cpp交換鏈接,喜歡

c++ b.cpp a.cpp -o result -std=c++17 && ./result 

結果將是

foobar called from b.cpp: 2 
foobar called from a.cpp: 2 

所以完全相反的情況:即得到了在中到列表中首先提到的實例被鏈接的對象文件將會存活。這種行爲是否定義在標準的某個地方?根據構建系統的不同,提及目標文件的順序可能是相當隨意的,但是,正如在這樣的例子中,它會導致非常不同的程序和可能的繁瑣錯誤。即使我嘗試通過添加

template double foobar<double>(double); 

,以明確地從實例化的a.cpp版本,它不會使foobar的<>模板從a.cpp在鏈接列表a.o前提B.O時生存。

+0

只是出於好奇:這是一個純粹的學術問題,還是你有真正的情況下,這是一個問題?我的意思是你可以只定義模板一次,問題就沒有了。 – user463035818

+0

@WernerHenze回答我的問題;) – user463035818

回答

3

我知道,模板,也有例外的ODR

有沒有例外ODR模板,它只是模板函數是inline

而你的程序違反了ODR。
您會遇到與常規內聯函數類似的問題。

+0

我可以重現那個。我不知道你顯然可以違反ODR並且很容易地運行到UB中,而鏈接器不會產生任何錯誤或警告,基本上只需添加內聯或模板函數定義即可。 – Jodocus