參考了以下兩篇文章:
問題來源:當模板文件的實現與聲明分開在不同文件中時,鏈接時會提示找不到相應模板函數,如下
一,編譯和鏈接的大概原理:
1,編譯,遍歷工程的所有代碼文件,進行文件分析,這里的分析與文件后綴無關,並不是說以CPP文件為依據,源文件后綴名可以改為任何名字。
編譯以文件為單位,將此文件#include的所有文件拿進來,寫進此文件中,包含進來的東西可能是函數聲明,也可能是函數的實現體。
如果#include "test.h",則包含進來的是一些函數和變量的聲明,如果 #include "test.cpp",則其中的函數實現代碼也被包含進來了。
編譯的結果是一個obj文件,如test.cpp編譯后是一個test.obj文件,里面是二進制的匯編指令。
此時,每個編譯單元編譯完成后,會提供三個表用於后面鏈接,這三個表分別是【未解決符號表】,【已解決符號表】,【重定向表】
已解決符號表是本編譯單元中定義的所有符號,包括變量和函數。
未解決符號表是本編譯單元中用到的外部符號,包括變量和函數。
重定向表是用於計算每個編譯單元在最終鏈接完成的EXE中的偏移地址。
2,鏈接,對所有的obj文件進行拼接。
為什么要拼接?對於每個obj文件,其中若調用了其它文件的函數(外部調用),就需要知道此外部函數的具體實現,這在編譯時是不關心的。
這時候去查找所有obj文件的【已解決符號表】中查找此外部函數的實現體,若有兩個以上的obj都有此實現,則鏈接出錯,因為函數實現不唯一了,這不允許。此錯誤就是常見的
XXX 已經在 xxx.obj中定義了,如下:
二,實例分析
1,一個頭文件被多個CPP包含時編譯鏈接正確,一個CPP文件被多個其它CPP文件包含時編譯正確,鏈接出錯,報錯為 XXX 已經在 xxx.obj中定義。
因為CPP中有函數的實現體,每被包含一次就多了一個實現,導致一個函數在不同CPP文件中被多次實現,重復了。
頭文件被多次包含為什么沒問題?關鍵是每個頭文件開頭都有宏 #pragma once,該宏確保了頭文件只會被包含一次
2,模板文件的特殊性。
模板文件只有在實例化時才能確定其具體的實現體,所以如果將模板文件的聲明和函數體分開在.h和.cpp中,當編譯cpp時,並不會產生函數的具體實現體。當在其它文件中#include "template.h"時,會提示找不到函數的定義。
解決方法:在需要使用模板函數的地方,#include "template.cpp",即包含它的CPP文件,而不是.h文件。
原因:使用模板函數的地方,比如 addobj<cube>(),傳了具體的模板類型給函數,這樣模板函數就能到CPP文件中找到對應的實現體將cube傳給模板參數而實例化了。
3,綜合的例子,若一個類中既有模板函數,又有非模板函數,那么只能將模板函數的聲明與定義寫在一個文件中,分開到兩個文件是不行的。
-------------------------------------------------------------------------------------------------------------------------------
編譯是以CPP為單元進行的,各個編譯單元之間相互獨立,互不感知。這也是聯合聯系和多編程編譯的基礎。
重復包含是什么?
重復包含是一個文件多次包含了另一個文件。
而多個文件都包含了同一個文件不是重復包含。
#pragma once的作用是防止x.h被a.cpp重復包含,而不是防止x.h被b.cpp包含后不再被c.cpp包含
文件 x.h
#pragma once
class CA{};
文件 a.cpp
#include "x.h"
#include "x.h"
文件 b.cpp
#include "x.h"
文件 c.cpp
#include "x.h"
原因要從符號表說起,每個CPP編譯時都會產生三個表,【已解決符號表】,【未解決符號表】,【重定向表】
1,如上面a.cpp的情況,包含了兩次 x.h,這時候x.h中聲明的相關符號就會在【已解決符號表】中重復,於是報錯。
2,對於上面 b.cpp, c.cpp的情況,由於二者有相互獨立的符號表,所以不會報重,不會報錯。
在鏈接時,是從所有符號表中查找相關符號的定義(實現),符號在多個符號表中聲明沒關系