最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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)系,可以類比“類”和“對象”的關(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;
 }

?對于每一個(gè)用到的具體類型,編譯器都會(huì)生成對應(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ù)對應(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):可以按需生成。假如我們在 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ò)誤。?

對于這種寫法,編譯器在編譯 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ò)誤疏漏之處在所難免,懇請廣大讀者批評指正,您的批評是在下前進(jìn)的不竭動(dòng)力,更多關(guān)于C++頭文件放入模板實(shí)現(xiàn)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • c++實(shí)現(xiàn)簡單隨機(jī)數(shù)的代碼

    c++實(shí)現(xiàn)簡單隨機(jī)數(shù)的代碼

    在本篇文章里小編給大家整理的是一篇關(guān)于c++實(shí)現(xiàn)簡單隨機(jī)數(shù)的代碼內(nèi)容,有需要的朋友們可以跟著學(xué)習(xí)下。
    2021-05-05
  • C++ 前置聲明詳解及實(shí)例

    C++ 前置聲明詳解及實(shí)例

    這篇文章主要介紹了C++ 前置聲明詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • 重構(gòu)-C++實(shí)現(xiàn)矩陣的簡單實(shí)例

    重構(gòu)-C++實(shí)現(xiàn)矩陣的簡單實(shí)例

    下面小編就為大家?guī)硪黄貥?gòu)-C++實(shí)現(xiàn)矩陣的簡單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-06-06
  • C++ 單鏈表的基本操作(詳解)

    C++ 單鏈表的基本操作(詳解)

    下面小編就為大家?guī)硪黄狢++ 單鏈表的基本操作(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-12-12
  • Qt實(shí)現(xiàn)UDP多線程數(shù)據(jù)處理及發(fā)送的簡單實(shí)例

    Qt實(shí)現(xiàn)UDP多線程數(shù)據(jù)處理及發(fā)送的簡單實(shí)例

    本文主要介紹了Qt實(shí)現(xiàn)UDP多線程數(shù)據(jù)處理及發(fā)送的簡單實(shí)例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • C++全面細(xì)致講解復(fù)數(shù)類

    C++全面細(xì)致講解復(fù)數(shù)類

    本文章向大家介紹C++ 標(biāo)準(zhǔn)庫中的復(fù)數(shù)類,主要包括C++ 標(biāo)準(zhǔn)庫中的復(fù)數(shù)類使用實(shí)例、應(yīng)用技巧、基本知識(shí)點(diǎn)總結(jié)和需要注意事項(xiàng),具有一定的參考價(jià)值,需要的朋友可以參考一下
    2022-06-06
  • c++ sqlite3如何利用事務(wù)(BEGIN;COMMIT;)批量操作

    c++ sqlite3如何利用事務(wù)(BEGIN;COMMIT;)批量操作

    這篇文章主要介紹了c++ sqlite3如何利用事務(wù)(BEGIN;COMMIT;)批量操作,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • C語言俄羅斯方塊游戲課程設(shè)計(jì)

    C語言俄羅斯方塊游戲課程設(shè)計(jì)

    這篇文章主要為大家詳細(xì)介紹了C語言俄羅斯方塊游戲課程設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-06-06
  • c++ For循環(huán)執(zhí)行順序流程圖解

    c++ For循環(huán)執(zhí)行順序流程圖解

    for 循環(huán)允許您編寫一個(gè)執(zhí)行特定次數(shù)的循環(huán)的重復(fù)控制結(jié)構(gòu),這里為大家分享一下具體的執(zhí)行流程,需要的朋友可以參考下
    2021-10-10
  • C語言-I/O流設(shè)計(jì)實(shí)驗(yàn)

    C語言-I/O流設(shè)計(jì)實(shí)驗(yàn)

    編程語言的I/O類庫中常常使用流這個(gè)抽象的概念,它代表任何有能力產(chǎn)生數(shù)據(jù)的數(shù)據(jù)源對象或時(shí)有能力接收數(shù)據(jù)的接收端對象,本文為大家介紹C語言中I/O系統(tǒng)基礎(chǔ)知識(shí)
    2021-07-07

最新評論

海丰县| 社旗县| 淄博市| 昔阳县| 秭归县| 彭山县| 葫芦岛市| 广昌县| 枞阳县| 米脂县| 南丹县| 开封县| 靖宇县| 汪清县| 上杭县| 宣城市| 伊宁市| 甘肃省| 成都市| 樟树市| 陇西县| 诸城市| 仪陇县| 白银市| 利辛县| 南阳市| 高邮市| 周口市| 临城县| 获嘉县| 廊坊市| 广宗县| 青河县| 诸城市| 板桥市| 固安县| 顺义区| 广南县| 诸暨市| 兰考县| 同仁县|