青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

為什么C++編譯器不能支持對模板的分離式編譯
劉未鵬(pongba) /文

首先,C++標準中提到,一個編譯單元[translation unit]是指一個.cpp文件以及它所include的所有.h文件,.h文件里的代碼將會被擴展到包含它的.cpp文件里,然后編譯器編譯該.cpp 文件為一個.obj文件,后者擁有PE[Portable Executable,即windows可執行文件]文件格式,并且本身包含的就已經是二進制碼,但是,不一定能夠執行,因為并不保證其中一定有main 函數。當編譯器將一個工程里的所有.cpp文件以分離的方式編譯完畢后,再由連接器(linker)進行連接成為一個.exe文件。
舉個例子:
//---------------test.h-------------------//
void f();//這里聲明一個函數f
//---------------test.cpp--------------//
#i nclude”test.h”
void f()
{
…//do something
} //這里實現出test.h中聲明的f函數
//---------------main.cpp--------------//
#i nclude”test.h”
int main()
{
f(); //調用f,f具有外部連接類型
}
在 這個例子中,test. cpp和main.cpp各被編譯成為不同的.obj文件[姑且命名為test.obj和main.obj],在main.cpp中,調用了f函數,然而 當編譯器編譯main.cpp時,它所僅僅知道的只是main.cpp中所包含的test.h文件中的一個關于void f();的聲明,所以,編譯器將這里的f看作外部連接類型,即認為它的函數實現代碼在另一個.obj文件中,本例也就是test.obj,也就是 說,main.obj中實際沒有關于f函數的哪怕一行二進制代碼,而這些代碼實際存在于test.cpp所編譯成的test.obj中。在 main.obj中對f的調用只會生成一行call指令,像這樣:
call f [C++中這個名字當然是經過mangling[處理]過的]
在 編譯時,這個call指令顯然是錯誤的,因為main.obj中并無一行f的實現代碼。那怎么辦呢?這就是連接器的任務,連接器負責在其它的.obj中 [本例為test.obj]尋找f的實現代碼,找到以后將call f這個指令的調用地址換成實際的f的函數進入點地址。需要注意的是:連接器實際上將工程里的.obj“連接”成了一個.exe文件,而它最關鍵的任務就是 上面說的,尋找一個外部連接符號在另一個.obj中的地址,然后替換原來的“虛假”地址。
這個過程如果說的更深入就是:
call f這行指令其實并不是這樣的,它實際上是所謂的stub,也就是一個
jmp 0x23423[這個地址可能是任意的,然而關鍵是這個地址上有一行指令來進行真正的call f動作。也就是說,這個.obj文件里面所有對f的調用都jmp向同一個地址,在后者那兒才真正”call”f。這樣做的好處就是連接器修改地址時只要對 后者的call XXX地址作改動就行了。但是,連接器是如何找到f的實際地址的呢[在本例中這處于test.obj中],因為.obj于.exe的格式都是一樣的,在這 樣的文件中有一個符號導入表和符號導出表[import table和export table]其中將所有符號和它們的地址關聯起來。這樣連接器只要在test.obj的符號導出表中尋找符號f[當然C++對f作了mangling]的 地址就行了,然后作一些偏移量處理后[因為是將兩個.obj文件合并,當然地址會有一定的偏移,這個連接器清楚]寫入main.obj中的符號導入表中f 所占有的那一項。
這就是大概的過程。其中關鍵就是:
編譯main.cpp時,編譯器不知道f的實現,所有當碰到對它的調用時只是給出一個指示,指示連接器應該為它尋找f的實現體。這也就是說main.obj中沒有關于f的任何一行二進制代碼。
編譯test.cpp時,編譯器找到了f的實現。于是乎f的實現[二進制代碼]出現在test.obj里。
連接時,連接器在test.obj中找到f的實現代碼[二進制]的地址[通過符號導出表]。然后將main.obj中懸而未決的call XXX地址改成f實際的地址。
完成。

然而,對于模板,你知道,模板函數的代碼其實并不能直接編譯成二進制代碼,其中要有一個“具現化”的過程。舉個例子:
//----------main.cpp------//
template<class T>
void f(T t)
{}
int main()
{
…//do something
f(10); //call f<int> 編譯器在這里決定給f一個f<int>的具現體
…//do other thing
}
也就是說,如果你在main.cpp文件中沒有調用過f,f也就得不到具現,從而main.obj中也就沒有關于f的任意一行二進制代碼!!如果你這樣調用了:
f(10); //f<int>得以具現化出來
f(10.0); //f<double>得以具現化出來
這樣main.obj中也就有了f<int>,f<double>兩個函數的二進制代碼段。以此類推。
然而具現化要求編譯器知道模板的定義,不是嗎?
看下面的例子:[將模板和它的實現分離]
//-------------test.h----------------//
template<class T>
class A
{
public:
void f(); //這里只是個聲明
};
//---------------test.cpp-------------//
#i nclude”test.h”
template<class T>
void A<T>::f() //模板的實現,但注意:不是具現
{
…//do something
}
//---------------main.cpp---------------//
#i nclude”test.h”
int main()
{
A<int> a;
a. f(); //編譯器在這里并不知道A<int>::f的定義,因為它不在test.h里面
//于是編譯器只好寄希望于連接器,希望它能夠在其他.obj里面找到
//A<int>::f的實現體,在本例中就是test.obj,然而,后者中真有A<int>::f的
//二進制代碼嗎?NO!!!因為C++標準明確表示,當一個模板不被用到的時
//侯它就不該被具現出來,test.cpp中用到了A<int>::f了嗎?沒有!!所以實
//際上test.cpp編譯出來的test.obj文件中關于A::f的一行二進制代碼也沒有
//于是連接器就傻眼了,只好給出一個連接錯誤
// 但是,如果在test.cpp中寫一個函數,其中調用A<int>::f,則編譯器會將其//具現出來,因為在這個點上[test.cpp 中],編譯器知道模板的定義,所以能//夠具現化,于是,test.obj的符號導出表中就有了A<int>::f這個符號的地
//址,于是連接器就能夠完成任務。
}

關鍵是:在分離式編譯的環境下,編譯器編譯某一個.cpp文件時并不知道另一個.cpp文件的存在,也不會去查找[當遇到未決符號時它會寄希望于連 接器]。這種模式在沒有模板的情況下運行良好,但遇到模板時就傻眼了,因為模板僅在需要的時候才會具現化出來,所以,當編譯器只看到模板的聲明時,它不能 具現化該模板,只能創建一個具有外部連接的符號并期待連接器能夠將符號的地址決議出來。然而當實現該模板的.cpp文件中沒有用到模板的具現體時,編譯器 懶得去具現,所以,整個工程的.obj中就找不到一行模板具現體的二進制代碼,于是連接器也黔

/////////////////////////////////
http://dev.csdn.net/develop/article/19/19587.shtm
 C++模板代碼的組織方式 ——包含模式(Inclusion Model)     選擇自 sam1111 的 Blog 
關鍵字   Template Inclusion Model
出處   C++ Template: The Complete Guide


說明:本文譯自《C++ Template: The Complete Guide》一書的第6章中的部分內容。最近看到C++論壇上常有關于模板的包含模式的帖子,聯想到自己初學模板時,也為類似的問題困惑過,因此翻譯此文,希望對初學者有所幫助。

模板代碼有幾種不同的組織方式,本文介紹其中最流行的一種方式:包含模式。

鏈接錯誤

大多數C/C++程序員向下面這樣組織他們的非模板代碼:

         ·類和其他類型全部放在頭文件中,這些頭文件具有.hpp(或者.H, .h, .hh, .hxx)擴展名。

         ·對于全局變量和(非內聯)函數,只有聲明放在頭文件中,而定義放在點C文件中,這些文件具有.cpp(或者.C, .c, .cc, .cxx)擴展名。
 

這種組織方式工作的很好:它使得在編程時可以方便地訪問所需的類型定義,并且避免了來自鏈接器的“變量或函數重復定義”的錯誤。
 

由于以上組織方式約定的影響,模板編程新手往往會犯一個同樣的錯誤。下面這一小段程序反映了這種錯誤。就像對待“普通代碼”那樣,我們在頭文件中定義模板:
 

// basics/myfirst.hpp 

#ifndef MYFIRST_HPP
#define MYFIRST_HPP 

// declaration of template

template <typename T>

void print_typeof (T const&);

#endif // MYFIRST_HPP

 

print_typeof()聲明了一個簡單的輔助函數用來打印一些類型信息。函數的定義放在點C文件中:

// basics/myfirst.cpp

#i nclude <iostream>

#i nclude <typeinfo>

#i nclude "myfirst.hpp"
 

// implementation/definition of template

template <typename T>
void print_typeof (T const& x)
{

    std::cout << typeid(x).name() << std::endl;

}

 

這個例子使用typeid操作符來打印一個字符串,這個字符串描述了傳入的參數的類型信息。

最后,我們在另外一個點C文件中使用我們的模板,在這個文件中模板聲明被#i nclude:

// basics/myfirstmain.cpp 

#i nclude "myfirst.hpp" 

// use of the template

int main()
{

    double ice = 3.0;
    print_typeof(ice);  // call function template for type double

}

 
大部分C++編譯器(Compiler)很可能會接受這個程序,沒有任何問題,但是鏈接器(Linker)大概會報告一個錯誤,指出缺少函數print_typeof()的定義。

這個錯誤的原因在于,模板函數print_typeof()的定義還沒有被具現化(instantiate)。為了具現化一個模板,編譯器必須知道 哪一個定義應該被具現化,以及使用什么樣的模板參數來具現化。不幸的是,在前面的例子中,這兩組信息存在于分開編譯的不同文件中。因此,當我們的編譯器看 到對print_typeof()的調用,但是沒有看到此函數為double類型具現化的定義時,它只是假設這樣的定義在別處提供,并且創建一個那個定義 的引用(鏈接器使用此引用解析)。另一方面,當編譯器處理myfirst.cpp時,該文件并沒有任何指示表明它必須為它所包含的特殊參數具現化模板定 義。

頭文件中的模板

解決上面這個問題的通用解法是,采用與我們使用宏或者內聯函數相同的方法:我們將模板的定義包含進聲明模板的頭文件中。對于我們的例子,我們可以通 過將#i nclude "myfirst.cpp"添加到myfirst.hpp文件尾部,或者在每一個使用我們的模板的點C文件中包含myfirst.cpp文件,來達到目 的。當然,還有第三種方法,就是刪掉myfirst.cpp文件,并重寫myfirst.hpp文件,使它包含所有的模板聲明與定義:


// basics/myfirst2.hpp

#ifndef MYFIRST_HPP
#define MYFIRST_HPP 

#i nclude <iostream>
#i nclude <typeinfo>
 

// declaration of template
template <typename T>
void print_typeof (T const&); 

// implementation/definition of template
template <typename T>
void print_typeof (T const& x)
{

    std::cout << typeid(x).name() << std::endl;

}

#endif // MYFIRST_HPP

 

這種組織模板代碼的方式就稱作包含模式。經過這樣的調整,你會發現我們的程序已經能夠正確編譯、鏈接、執行了。

從這個方法中我們可以得到一些觀察結果。最值得注意的一點是,這個方法在相當程度上增加了包含myfirst.hpp的開銷。在這個例子中,這種開 銷并不是由模板定義自身的尺寸引起的,而是由這樣一個事實引起的,即我們必須包含我們的模板用到的頭文件,在這個例子中 是<iostream>和<typeinfo>。你會發現這最終導致了成千上萬行的代碼,因為諸 如<iostream>這樣的頭文件也包含了和我們類似的模板定義。

這在實踐中確實是一個問題,因為它增加了編譯器在編譯一個實際程序時所需的時間。我們因此會在以后的章節中驗證其他一些可能的方法來解決這個問題。但無論如何,現實世界中的程序花一小時來編譯鏈接已經是快的了(我們曾經遇到過花費數天時間來從源碼編譯的程序)。

拋開編譯時間不談,我們強烈建議如果可能盡量按照包含模式組織模板代碼。

另一個觀察結果是,非內聯模板函數與內聯函數和宏的最重要的不同在于:它并不會在調用端展開。相反,當模板函數被具現化時,會產生此函數的一個新的 拷貝。由于這是一個自動的過程,編譯器也許會在不同的文件中產生兩個相同的拷貝,從而引起鏈接器報告一個錯誤。理論上,我們并不關心這一點:這是編譯器設 計者應當關心的事情。實際上,大多數時候一切都運轉正常,我們根本就不用處理這種狀況。然而,對于那些需要創建自己的庫的大型項目,這個問題偶爾會顯現出 來。
 

最后,需要指出的是,在我們的例子中,應用于普通模板函數的方法同樣適用于模板類的成員函數和靜態數據成員,以及模板成員函數。



Posted on 2008-10-09 17:23 micheal's tech 閱讀(1916) 評論(0)  編輯 收藏 引用 所屬分類: C++ programme language
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美aa在线视频| 欧美日本亚洲视频| 黄色工厂这里只有精品| 久久精品欧美日韩| 久久爱www| 91久久精品一区| av成人福利| 国产伪娘ts一区| 欧美成人久久| 欧美精品自拍| 欧美一级欧美一级在线播放| 亚洲欧美日韩一区二区三区在线观看 | 另类专区欧美制服同性| 久久亚洲欧洲| 亚洲男人av电影| 久久精品免费电影| 一区二区高清在线观看| 午夜精品一区二区三区四区| 亚洲激情不卡| 亚洲欧美日韩国产成人精品影院| 一区二区三区中文在线观看| 亚洲丰满少妇videoshd| 欧美日韩在线观看视频| 久久视频免费观看| 欧美日本中文字幕| 久久久久久亚洲精品杨幂换脸 | 久久久噜噜噜久久中文字免| 亚洲毛片在线观看| 欧美在线免费观看视频| 日韩一级黄色片| 久久精品亚洲热| 午夜精品www| 嫩草国产精品入口| 欧美在线一区二区三区| 欧美精品亚洲一区二区在线播放| 欧美在线高清视频| 欧美日韩中文字幕综合视频 | 亚洲深夜av| 噜噜噜躁狠狠躁狠狠精品视频| 亚洲综合电影一区二区三区| 欧美r片在线| 久久男人av资源网站| 欧美特黄a级高清免费大片a级| 欧美大片专区| 国产在线一区二区三区四区| 一区二区黄色| 在线视频日韩| 欧美黄色成人网| 欧美成人免费小视频| 国产亚洲精品一区二区| 在线视频精品一区| 一区二区三区回区在观看免费视频| 久久精品一区四区| 久久国产主播| 国产欧美日韩激情| 亚洲一区三区在线观看| 亚洲午夜一二三区视频| 欧美激情一区二区三区在线视频| 麻豆视频一区二区| 精品不卡视频| 久热精品视频在线免费观看 | 一本色道久久综合亚洲二区三区| 噜噜噜91成人网| 欧美成人有码| 亚洲人在线视频| 欧美成人精品福利| 亚洲国产99| 99精品视频网| 国产精品a久久久久| 正在播放亚洲一区| 欧美一区网站| 狠狠狠色丁香婷婷综合激情| 久久精品成人一区二区三区蜜臀 | 亚洲激情在线观看| 欧美国产欧美亚州国产日韩mv天天看完整| 欧美大色视频| 一区二区福利| 国产欧美视频一区二区三区| 午夜性色一区二区三区免费视频| 久久久精品2019中文字幕神马| 国产一区二区久久| 美女精品网站| 一区二区三区蜜桃网| 欧美一区二区三区视频| 狠狠色丁香久久婷婷综合_中| 久久免费视频在线观看| 亚洲欧洲精品成人久久奇米网| 99在线热播精品免费| 国产精品萝li| 久久久综合网站| 亚洲精品久久嫩草网站秘色 | 欧美刺激性大交免费视频| 最新国产拍偷乱拍精品| 欧美日韩在线大尺度| 午夜国产精品影院在线观看| 欧美成人黄色小视频| 亚洲视频一区二区| 国户精品久久久久久久久久久不卡| 久久影视三级福利片| 亚洲国内精品| 香蕉国产精品偷在线观看不卡| 好吊色欧美一区二区三区视频| 欧美成人免费大片| 亚洲一区二区三区四区在线观看 | 99国产精品国产精品久久| 国产精品国产亚洲精品看不卡15| 久久高清一区| 一本大道久久a久久精二百| 久久久久久久综合| 亚洲视频在线免费观看| 韩日精品中文字幕| 国产精品久久久久高潮| 蜜臀久久久99精品久久久久久| 亚洲一区二区三区777| 欧美激情中文不卡| 久久久久久久久久久久久久一区| 一区二区国产在线观看| 极品日韩久久| 国产日韩一区二区| 欧美三日本三级三级在线播放| 久久青草福利网站| 欧美一区二区日韩一区二区| 日韩视频永久免费| 亚洲黄一区二区三区| 美女视频黄a大片欧美| 欧美在线三级| 新狼窝色av性久久久久久| 一本色道久久| 日韩视频在线你懂得| 亚洲区在线播放| 在线观看亚洲专区| 国外成人免费视频| 国产婷婷色一区二区三区四区| 国产精品多人| 国产精品久久毛片a| 欧美午夜精品一区| 欧美日韩在线三级| 欧美午夜精品伦理| 欧美视频在线观看| 欧美午夜a级限制福利片| 欧美精品日韩| 欧美日韩国产123区| 欧美日韩精品三区| 欧美精品一区二区蜜臀亚洲| 欧美成人性网| 欧美日本不卡高清| 欧美三级视频| 国产毛片久久| 国内精品久久久久久 | 国产午夜精品久久久久久免费视| 国产精品美女久久久久久久| 国产精品乱码一区二区三区| 国产精品亚洲片夜色在线| 国产伦精品一区二区三区照片91| 国产毛片精品国产一区二区三区| 国产日韩精品入口| 影音先锋久久| 亚洲三级电影全部在线观看高清| 亚洲日本一区二区| 亚洲一品av免费观看| 午夜亚洲性色福利视频| 久久精视频免费在线久久完整在线看| 久久精品免费电影| 亚洲成人在线视频播放| 欧美风情在线观看| 日韩午夜av在线| 亚洲欧美日韩国产中文| 久久人体大胆视频| 欧美男人的天堂| 国产情侣久久| 亚洲国产国产亚洲一二三| 一区二区三区视频在线播放| 小处雏高清一区二区三区 | 欧美在线综合视频| 欧美大片在线观看一区| 亚洲色图综合久久| 久久久久久久网站| 欧美日韩一区在线观看视频| 国内久久婷婷综合| 日韩一区二区免费高清| 欧美在线视频观看| 亚洲国产精品久久久| 亚洲主播在线播放| 免费一级欧美片在线播放| 国产精品高潮呻吟久久| 亚洲高清色综合| 午夜一区在线| 亚洲国产日韩欧美在线动漫| 亚洲欧美日韩精品久久久久| 欧美成人久久| 韩国亚洲精品| 亚洲女女做受ⅹxx高潮| 亚洲国产成人精品女人久久久| 亚洲自拍偷拍视频| 欧美激情成人在线| 激情综合亚洲| 久久精品国产久精国产爱| 99re热这里只有精品免费视频| 久久免费精品日本久久中文字幕| 国产精品免费一区二区三区观看 |