模版的編譯


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