能否從編譯原理的角度詳細的描述一下模板編譯的過程?

以及 能夠順便從編譯原理的角度講解一下模板編譯時, 為什麼寫.cpp文件時linker會報錯?


這個問題很大,而我也其實到現在也沒有完整的,清晰的弄清楚模板在編譯器的實現。但是針對這個問題,我想我可以簡單的回答一下你提到的linker為什麼會報錯。我希望我可以盡量用簡單的語言來敘述,讓看的人都能理解。

首先,模板和class,function都不同,模板是一個模式,它可以讓編譯器利用這個模式來生成一些列的class和function,這是最本質最重要的一點。於是,要讓編譯器生成完整的代碼,編譯器需要看見模板的定義和所需要填充的類型,這樣編譯器才能生成"正常"的代碼。

然後回到你的問題,你所提到的寫在一個cpp裡面,那麼更延伸一點兒莫過於.h放置聲明,.cpp放置實現。

舉一個栗子

// x.h
#pragma once
template &
class X
{
public:
void foo(T t);
};

// x.cpp
#include "x.h"
template &
void X&::foo(T t) { /* do something */ }

// main.cpp
#include "x.h"
int main()
{
X& x;
x.foo("a");
}

於是乎,我們知道了T的類型為char,我們需要利用這一個定義好的模式來生成x的構造函數以及foo函數,其中要填充的類型是char。然而很遺憾的是,我們編譯器在編譯x.cpp的時候才看見模板的定義,而在編譯main.cpp的時候才看見X&,明白要填充的類型是char。編譯器沒有辦法同時看到模板的定義和X&,於是也就沒有辦法生成X&::foo(char t)。

接下來,或許就是你疑惑的地方了,為什麼編譯器不知道把main.cpp的char拿到x.cpp那裡去,為什麼linker會報錯。

我們的編譯器比較「愚蠢」,每次它編譯完一個cpp文件,當它編譯另一個cpp文件的時候它就忘記了其他cpp文件的信息了,所以即使x.cpp有模板定義,main.cpp有X&,似乎所需要的信息都齊全了,只是很可惜它們不再一個地方,無法讓編譯器同時看到。若這樣可以做,那就是所謂的模板分離編譯模型,曾經有個關鍵字叫做export,讓模板聲明和實現可以分開,不過只有EDG做出來了,然後告訴大家不要做export,現在此關鍵字已被C++11廢棄,留作後續使用(或許會用於C++1z的Modules System的介面導出作用)。

那麼為什麼會在linker階段報錯呢?在main.cpp中,我們有X&.foo("a"),然而foo的定義不在x.h中,於是編譯器只好把這個工作交給linker,讓linker去其它的obj文件找尋foo的定義。而在哪裡找呢?就是在編譯產生x.cpp產生出的x.o裡面找,然而很遺憾,x.o裡面沒有任何X&::foo(char)的信息,因為C++標準規定,若模板不被用時,不應該實例化它。而x.cpp沒有使用X&,所以linker找不到定義,於是就報錯undefined reference to `X&::foo(char)"。

所以,針對上述例子,若我們在x.cpp加一個實例化char類型的,即可讓程序通過,如

// x.cpp
#include "x.h"
template &
void X&::foo(T t) { /* do something */ }

void bar()
{
X& xx;
xx.foo("a");
}

這樣的話,我們的linker在x.o中就能找到相關的信息了。

那麼除了上文說的export關鍵字,還有沒有可以解決linker的錯誤呢?C++11引入了extern template,如這樣

// x.cpp
#include "x.h"
template &
void X&::foo(T t) { /* do something */ }

template class X&; // make available for the clients

// main.cpp
#include "x.h"

// I know, someelse where must define X&.
extern template class X&;
int main()
{
X& x;
x.foo("a");
}

然而,extern template更重要的目的其實是降低冗餘的模板實例化代碼生成,可以參見C++提案Adding "extern template" (version 2) 和我同事在13年寫的一篇博客 The XL C/C++ V11.1 compiler supports extern templates (C/C++ Cafe)


模版就是函數+佔位符,假設佔位符有N種可能,那麼編譯的時候,就要生成N個函數。 具體調用哪個函數,由實際傳入的參數來決定。 模版不是動態編譯,所以有很多限制。


先把模版展開然後編譯,c++是個強類型靜態語言

你把定義寫道cpp裡面linker肯定報錯,簡單來說就是頭文件展開後找不到定義了


可以買一本c++ templates 裡面比較詳細


推薦閱讀:

編譯程序是否有操作系統的參與?
編譯器、解釋器和虛擬機有什麼區別和聯繫?大體原理是什麼?
編譯器處理轉義符?
Android上ARM本地庫是如何運行在其他CPU架構上的?
Linux/Unix中查看一個C/C++大工程中所有函數的調用順序,有哪些方法?

TAG:C | 編譯原理 | 泛型Generic | 模板C |