模版的編譯

c++ primer p657

- 一般來說,如果你的項目沒有混合使用 C 和 C++ 語言,那么你使用 .h 和 .cpp 是沒有問題的。否則你將C和C++的頭文件進行分離,因為通常我們把C和C++分離編譯,再統(tǒng)一鏈接;
- 函數(shù)經(jīng)過編譯系統(tǒng)的翻譯成匯編,函數(shù)名對應著匯編標號。 因為C編譯函數(shù)名與得到的匯編代號基本一樣,如:fun()=>_fun, main=>_main ;但是C++中函數(shù)名與得到的匯編代號有比較大的差別。 如:由于函數(shù)重載,函數(shù)名一樣,但匯編代號絕對不能一樣。
- 為了區(qū)分,編譯器會把函數(shù)名和參數(shù)類型合在一起作為匯編代號, 這樣就解決了重載問題。具體如何把函數(shù)名和參數(shù)類型合在一起, 要看編譯器的幫助說明了。
- 這樣一來,如果C++調(diào)用C,如fun(),則調(diào)用名就不是C的翻譯結果_fun, 而是帶有參數(shù)信息的一個名字,因此就不能調(diào)用到fun(),為了解決 這個問題,加上extern "C"表示該函數(shù)的調(diào)用規(guī)則是C的規(guī)則,則調(diào)用 時就不使用C++規(guī)則的帶有參數(shù)信息的名字,而是_fun,從而達到調(diào)用 C函數(shù)的目的。
具體看下面的栗子:
//test.h
#ifndef ALGORITHMS_TEST_H
#define ALGORITHMS_TEST_H
template <typename T>
class Foo
{
T bar;
public:
//void doSomething(T param) {/* do stuff using T */}
void doSomething(T param);
};
#endif //ALGORITHMS_TEST_H
//test.cpp
#include<iostream>
#include "test.h"
template <typename T>
void Foo<T>::doSomething(T param) {
std::cout << param << std::endl;
}
//main.cpp
#include "test.h"
int main() {
Foo<int> f;
f.doSomething(3);
return 0;
}
編譯報連接錯誤
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[3]: *** [Algorithms] Error 1
為什么模版類的聲明和實現(xiàn)分開會出現(xiàn)連接問題呢
因為需要單獨編譯,并且模板是實例化樣式的多態(tài)性;
在實例化模板時,編譯器會使用給定的模板參數(shù)創(chuàng)建一個新類。例如:
template<typename T>
struct Foo
{
T bar;
void doSomething(T param) {/* do stuff using T */}
};
// somewhere in a .cpp
Foo<int> f;
閱讀此行時,編譯器將創(chuàng)建一個新類(我們稱之為FooInt),其等效于以下內(nèi)容:
struct FooInt
{
int bar;
void doSomething(int param) {/* do stuff using int */}
}
因此,編譯器需要訪問方法的實現(xiàn),以使用template參數(shù)實例化它們(在這種情況下為int)。如果這些實現(xiàn)不在頭文件中,則將無法訪問它們,因此編譯器將無法實例化模板。
具體解釋如下:
foo.h
聲明的接口 class MyClass<T>
foo.cpp
定義執(zhí)行 class MyClass<T>
bar.cpp
用途 MyClass<int>
- 單獨編譯意味著我應該能夠獨立于bar.cpp編譯foo.cpp。編譯器在每個編譯單元上完全獨立地完成分析、優(yōu)化和代碼生成的所有艱苦工作;我們不需要做整個程序分析。只有鏈接器需要一次處理整個程序,鏈接器的工作大大簡化了。
- 當我編譯foo.cpp時,bar.cpp甚至不需要存在,但是我仍然應該可以將foo.o與bar.o一起使用。剛剛生成的文件,而無需重新編譯foo.cpp。甚至可以將foo.cpp編譯成動態(tài)庫,而無需foo.cpp即可將其分發(fā)到其他位置,并與在我編寫foo.cpp之后多年編寫的代碼鏈接。
- "實例化樣式多態(tài)性”表示模板MyClass<T>并不是真正的通用(范型)類,它不能被編譯成可以處理任何T值的代碼。這將增加開銷,如裝箱,需要傳遞函數(shù)指針到分配器和構造器,等等。c++模板的目的是避免編寫幾乎相同的類MyClass_int、類MyClass_float等,但仍然能夠編譯代碼,結束了就像我們分別編寫每個版本一樣。因此,模板實際上是模板。類模板不是類,而是為T我們遇到的每個類創(chuàng)建新類的秘訣。模板不能被編譯成代碼,只有實例化模板的結果可以被譯。
- 因此,在編譯foo.cpp時,編譯器無法通過看到bar.cpp來知道需要MyClass<int>。它可以看到模板MyClass<T>,但不能為此編譯出相應的代碼(它是模板,而不是類)。并且在編譯bar.cpp時,編譯器可以看到它需要創(chuàng)建一個MyClass<int>,但是看不到該模板MyClass<T>(只能在foo.h中看到其接口,不能看到具體類成員函數(shù)的具體實現(xiàn)等),因此無法創(chuàng)建它。
- 如果Foo.cpp中本身使用MyClass<int>,將在編譯時會產(chǎn)生對應代碼在Foo.cpp中,因此當文件bar.o鏈接到文件foo.o他們可以連接并工作。我們可以利用這一事實,通過編寫單個模板,在.cpp文件中實現(xiàn)一組有限的模板實例化。但bar.cpp無法將模板作為模板實例化它的類型;它只能使用foo.cpp的作者認為提供的模板化類的已存在版本。
- 您可能會認為,在編譯模板時,編譯器應“生成所有版本”,并且在鏈接過程中會濾除從未使用過的版本。除了龐大的開銷和極端的困難之外,這種方法還會面臨困難,因為指針和數(shù)組之類的“類型修飾符”功能甚至允許內(nèi)置類型產(chǎn)生無限數(shù)量的類型,當我現(xiàn)在擴展程序時會發(fā)生什么通過添加:
baz.cpp
聲明并實現(xiàn)class BazPrivate,并使用MyClass<BazPrivate>
除非我們:
- 1、每當我們改變程序中的任何其他文件時,必須重新編譯foo.cpp,以防它添加了一個新的實例化MyClass<T>
- 2、要求baz.cpp包含(可能通過標頭包含)的完整模板MyClass<T>,以便編譯器可以MyClass<BazPrivate>在編譯baz.cpp時生成。
沒有人喜歡(1),因為整個程序分析的編譯系統(tǒng)需要很長時間來編譯,而且如果沒有源代碼,就不可能分發(fā)已編譯的庫。所以我們用(2)代替。
解決方案
- 常見的解決方案是將模板聲明寫入頭文件中,然后在實現(xiàn)文件中實現(xiàn)該類(例如.tpp),并在頭末尾包含該實現(xiàn)文件。
//test.h
template <typename T>
struct Foo
{
void doSomething(T param);
};
#include "test.tpp"
//test.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}
這樣,實現(xiàn)仍與聲明分開,但編譯器可以訪問;
另外一種代替的解決方案:使實現(xiàn)分離,并顯式實例化所需的所有模板實例:
//Foo.h
//no implementation
//template <typename T> struct Foo { ... };
template <typename T>
class Foo
{
T bar;
public:
//void doSomething(T param) {/* do stuff using T */}
void doSomething(T param);
};
//Foo.cpp
// implementation of Foo's methods
// explicit instantiations
template <typename T>
void Foo<T>::doSomething(T param) {
std::cout << param << std::endl;
}
template class Foo<int>; //相關的實例化代碼由編譯器產(chǎn)生
template class Foo<float>;
//main.cpp
#include "test.hpp"
int main(int argc, const char * argv[]) {
Foo<int> f;
f.doSomething(3);
return 0;
}
/* 編譯運行成功:
3 Program ended with exit code: 0
*/
參考:https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file