欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++將模板實(shí)現(xiàn)放入頭文件原理解析

 更新時(shí)間:2022年06月06日 09:12:24   作者:同勉共進(jìn)  
這篇文章主要為大家介紹了C++將模板實(shí)現(xiàn)放入頭文件原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

寫在前面

本文通過實(shí)例分析與講解,解釋了為什么C++一般將模板實(shí)現(xiàn)放在頭文件中。這主要與C/C++的編譯機(jī)制以及C++模板的實(shí)現(xiàn)原理相關(guān),詳情見正文。同時(shí),本文給出了不將模板實(shí)現(xiàn)放在頭文件中的解決方案。

例子

現(xiàn)有如下3個(gè)文件:

// add.h
 template <typename T>
 T Add(const T &a, const T &b);
 // add.cpp
 #include "add.h"
 template <typename T>
 T Add(const T &a, const T &b)
 {
     return a + b;
 }
 // main.cpp
 #include "add.h"
 #include <iostream>
 int main()
 {
     int res = Add<int>(1, 2);
     std::cout << res << "\n";
     return 0;
 }

現(xiàn)象

使用 g++ -c add.cpp 編譯生成 add.o ,使用 g++ -c main.cpp 編譯生成 main.o ,這兩步都沒有問題。

使用 g++ -o main.exe main.o add.o 生成 main.exe 時(shí),報(bào)錯(cuò) undefined reference to 'int Add(int const&, int const&)' 。

當(dāng)然,直接 g++ add.cpp main.cpp -o main.exe 肯定也會(huì)報(bào)錯(cuò),這里把編譯和鏈接分開是為了更好地展示與分析問題。?

原因

出現(xiàn)上述問題的原因是:

(1)C/C++源文件是按編譯單元(translation unit)分開、獨(dú)立編譯的。所謂translation unit,其實(shí)就是輸入給編譯器的source code,只不過該source code是經(jīng)過預(yù)處理(pre-processed?,包括去掉注釋、宏替換、頭文件展開)的。在本例中,即便你使用 g++ add.cpp main.cpp -o main.exe ,編譯器也是分別編譯 add.cpp 和 main.cpp (注意是預(yù)處理后的)的。在編譯 add.cpp 時(shí),編譯器根本感知不到 main.cpp 的存在,反之同理。

(2) C++模板是通過實(shí)例化(instantiation)來實(shí)現(xiàn)多態(tài)(polymorphism)的。以函數(shù)模板為例,首先需要區(qū)分“函數(shù)模板”和“模板函數(shù)”。本例中,上面代碼的第8~12行是函數(shù)模板,顧名思義,它就是一個(gè)模子,不是具體的函數(shù),是不能運(yùn)行的;當(dāng)用具體的類型,如 int ,實(shí)例化模板參數(shù) T 后,會(huì)生成函數(shù)模板的一個(gè)具體實(shí)例,稱為模板函數(shù),這是真正可以運(yùn)行的函數(shù)。“函數(shù)模板”和“模板函數(shù)”的關(guān)系,可以類比“類”和“對(duì)象”的關(guān)系。以 int 為例,生成的實(shí)例/模板函數(shù)大概長這樣(細(xì)節(jié)上肯定和編譯器的實(shí)際實(shí)現(xiàn)有出入,但核心意思不會(huì)變)。

 int Add_int_int(const int &a, const int &b)
 {
     return a + b;
 }

?對(duì)于每一個(gè)用到的具體類型,編譯器都會(huì)生成對(duì)應(yīng)版本的實(shí)例,當(dāng)函數(shù)調(diào)用時(shí),會(huì)調(diào)用到該實(shí)例。如用到了 Add<int> ,就會(huì)生成 Add_int_int ,用到了 Add<double> ,就會(huì)生成 Add_double_double ,等等。本例中,當(dāng)編譯器編譯到第20行,即 int res = Add<int>(1,2); 一句時(shí),編譯器就會(huì)試圖生成 int 版本的模板實(shí)例(即模板函數(shù))。

(3)編譯器為模板生成實(shí)例的必要條件是:1. 知道模板的具體定義/實(shí)現(xiàn);2. 知道模板參數(shù)對(duì)應(yīng)的實(shí)際類型。

分析

下面把上面兩節(jié)內(nèi)容結(jié)合起來分析。

(1)當(dāng)編譯 add.cpp 時(shí),相當(dāng)于編譯

 template <typename T>
 T Add(const T &a, const T &b);
 template <typename T>
 T Add(const T &a, const T &b)
 {
     return a + b;
 }

此時(shí)編譯器雖然知道模板的具體定義,卻不知道模板參數(shù) T 的具體類型,因此不會(huì)生成任何的實(shí)例化代碼。

(2)當(dāng)編譯 main.cpp 時(shí),相當(dāng)于編譯

 #include <iostream>
 template <typename T>
 T Add(const T &a, const T &b);
 int main()
 {
     int res = Add<int>(1, 2);
     std::cout << res << "\n";
     return 0;
 }

當(dāng)編譯到 int res = Add<int>(1, 2); 時(shí),編譯器想要生成 int 版本的函數(shù)實(shí)例,但它找不到函數(shù)模板的具體定義(即 Add 的“函數(shù)體”),只好作罷。好在編譯器看到了函數(shù)模板的聲明,于是通過了編譯,將尋找 int 版本函數(shù)實(shí)例的任務(wù)留給了鏈接器。?

至此,編譯 add.cpp 時(shí),只知模板定義,不知模板類型參數(shù),無法生成具體的函數(shù)定義;編譯 main.cpp 時(shí),只知模板類型參數(shù),不知模板定義,同樣無法生成具體的函數(shù)定義。?

(3)沒什么好說的,鏈接器在 add.o 和 main.o 中都沒找到 int 版本的 Add 定義,直接報(bào)錯(cuò)。?

解決方案

方案一

傳統(tǒng)方法:把模板實(shí)現(xiàn)也放在頭文件中。

// add.h
 template <typename T>
 T Add(const T &a, const T &b)
 {
     return a + b;
 }
 // main.cpp
 #include "add.h"
 #include <iostream>
 int main()
 {
     int res = Add<int>(1, 2);
     std::cout << res << "\n";
     return 0;
 }

當(dāng)編譯 main.cpp 時(shí),相當(dāng)于編譯?

 #include <iostream>
 template <typename T>
 T Add(const T &a, const T &b)
 {
     return a + b;
 }
 int main()
 {
     int res = Add<int>(1, 2);
     std::cout << res << "\n";
     return 0;
 }

此時(shí)編譯器既知道函數(shù)模板的定義,又知道具體的模板類型參數(shù) int ,因此可以生成 int 版本的函數(shù)實(shí)例,不會(huì)出錯(cuò)。?

這種方式的優(yōu)缺點(diǎn)如下:

  • 優(yōu)點(diǎn):可以按需生成。假如我們?cè)?nbsp;main.cpp 中調(diào)用了 Add<double>(1.0, 2.0); ,編譯器就會(huì)為我們生成 double 版本的函數(shù)實(shí)例。
  • 缺點(diǎn):不得不把實(shí)現(xiàn)細(xì)節(jié)暴露給用戶。

方案二

模板聲明和定義分離的方案。?

 // add.h
 template <typename T>
 T Add(const T &a, const T &b);
 // add.cpp
 #include "add.h"
 template <typename T>
 T Add(const T &a, const T &b)
 {
     return a + b;
 }
 template int Add(const int &a, const int &b);
 // main.cpp
 #include "add.h"
 #include <iostream>
 int main()
 {
     int res = Add<int>(1, 2);
     std::cout << res << "\n";
     return 0;
 }

注意, template int Add(const int &a, const int &b); 是函數(shù)模板實(shí)例化(function template instantiation)[1], template 關(guān)鍵字不能省略,否則, int Add(const int &a, const int&b); 會(huì)被編譯器當(dāng)做普通函數(shù)的聲明,從而在鏈接時(shí)又會(huì)報(bào) undefined reference to 'int Add(int const&, int const&)' 錯(cuò)誤。?

對(duì)于這種寫法,編譯器在編譯 add.cpp 時(shí),既能看到函數(shù)模板的定義,又能看到具體的模板類型參數(shù) int ,于是生成了 int 版本的函數(shù)實(shí)例,整個(gè)程序可以正常編譯運(yùn)行。?

很顯然,這種情況下編譯器只生成了 int 版本的函數(shù)實(shí)例,所以,在 main.cpp 中使用 Add<double>(1.0, 2.0); 這樣的代碼肯定是不可以的。這種情況的優(yōu)缺點(diǎn)可以辯證看待:?

優(yōu)點(diǎn):

  • 1. 可以隱藏實(shí)現(xiàn)細(xì)節(jié)(我們可以把 add.cpp 做成.lib或.dll);
  • 2. 也可以限制只實(shí)例化特定的版本。?

缺點(diǎn):就是只能使用特定的幾個(gè)版本,不能像方案一那樣在編譯 main.cpp 時(shí)根據(jù)具體的調(diào)用情況按需生成。?

從這里也可以看出,模板實(shí)現(xiàn)不一定非得放在頭文件中。

參考

[1] Function template - cppreference.com

[2] c++ - Why can templates only be implemented in the header file? - Stack Overflow

寫在后面

本文從C/C++編譯機(jī)制以及C++模板實(shí)現(xiàn)原理的角度,結(jié)合具體實(shí)例,講解了為什么一般將模板實(shí)現(xiàn)放在頭文件中。由于在下才疏學(xué)淺,能力有限,錯(cuò)誤疏漏之處在所難免,懇請(qǐng)廣大讀者批評(píng)指正,您的批評(píng)是在下前進(jìn)的不竭動(dòng)力,更多關(guān)于C++頭文件放入模板實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論