1. 程式人生 > >深入探討vc下C++模板編譯模型

深入探討vc下C++模板編譯模型

寫過模板的朋友也許知道,一個模板程式,當編譯器看到模板定義時並不立即產生程式碼,只有在我們用到模板,並對其例項化的時候,才會產生特定的例項。此時,編譯器就要訪問定義模板的原始碼了。如果原始碼不可訪問,當然,編譯器會報錯的。記得我初學的時候,採用的是直接將宣告和實現全部放在一個.h裡面這個方法。但是,有時候我們確實想在.h檔案中宣告,在CPP檔案中實現,從而實現檔案分離。那麼下面我就寫寫一般模板函式,模板類,模板特化的檔案分離,我自己的心得。

         在《C++primer》 中講解了C++ 編譯模板程式碼的兩種模型 :(1)包含編譯  (2)分別編譯

(1)包含編譯模型:可以通過在宣告函式模板或類模板的標頭檔案中新增一條#include指示使定義可用,

從而引入包含相關定義的原始檔


// header file utlities.h
#ifndef UTLITIES_H
#define UTLITIES_H
template <class T> int compare(const T&, const T&);
......
#include "utilities.cpp"
#endif
//implementation file utlities.cpp
template <class T> int compare(const T &v1,const T &v2)
{
  //implemente
  ......
}

        這一策略,實現了標頭檔案和原始檔的分離

(2)分別編譯: 在分別編譯模式下,函式模板的宣告被放在標頭檔案中。在這種模式下,函式模板宣告和定義的組織方式與程式中的非行內函數的宣告和定義組織方式相同。分別編譯模型 只是在使用了關鍵字"export"來告訴編譯器模板定義在哪裡引用.

如果在標頭檔案類宣告中使用了export,則該標頭檔案只能被原始檔使用一次;如果在實現檔案中使用了export,有下面兩種用法

匯出類

      // XXXXX.H 檔案中 定義類  
   template <typename Type> class Test{/*...*/};  
   // 在XXXXX.CPP 檔案中  
   export template <typename Type> class Test;  
  #include"XXXXX.h"  
     ...//實現類成員<

匯出類成員函式,只用分別對成員使用export.
      // XXXXX.H 檔案中 只宣告
   template <typename Type> Type max( Type t1, Type t2 );
   // 在XXXXX.CPP 檔案中
   // 模板定義
   export template <typename Type>
   Type max( Type t1, Type t2 ) {/* . . . */}
   

看到這兒,你也許心花怒放,似乎覺得如此簡單。好,於是你回家敲程式碼,你會發現,你的編譯器VS照樣可能告訴你錯了!

       首先告訴你,一部分原因是編譯器問題,VS並不支援分別編譯。也就是說,如果你按照上面分別編譯的策略來編寫程式碼,編譯器會告訴你,目前我們不提供對關鍵字export的支援,也許將來會提供的支援的。不僅如此 vc的各個版本對C++的標準模板也支援程度也不盡相同,如vc6.0 並不支援模板的部分特化 等...

      然後也許你採用的是第一種包含編譯,包含編譯是所有編譯器都支援的,但是你會發現,仍然有錯。我們來看下面的一段程式碼:

在標頭檔案中

#ifndef tmp_h
#define tmp_h
#include <iostream>
#include<vector>
using namespace std;

template<typename T> 
class Worker
{
public:
	typename vector<T>::size_type sz;
	T test(const T& t1);
};

#include "tmp.cpp"
#endif
在CPP檔案中
#include"tmp.h"

template<typename ch>
void Display(string str);//這個函式是我在其他檔案中實現的一個函式,不影響我們討論的結果

template<typename T>
T Worker<T>::test(const T& t1)
{
	string str = "worker::test is Running!";
	::Display<char>(str);
	return t1;
}
然後在另一個檔案中呼叫
void main()
{
	 Worker<int> w1;
	 w1.test(12.0);
	 _getch();
}

上面的程式碼滿足C++ primer的策略吧,但是編譯器 就是報錯了:

error C2995: 'T Worker<T>::test(const T &)' : function template has already been defined

因為我們在標頭檔案中顯示的包含了相應的CPP檔案,但是如果我們多個檔案包含了這個標頭檔案,CPP檔案也同樣會被編譯多次。所以出現了上述的那些錯誤。為了防止包含編譯帶來的上述錯誤,我們將CPP檔案這樣修改下:

#ifndef tmp_cpp
#define tmp_cpp
#include"tmp.h"

template<typename ch>
void Display(string str);

template<typename T>
T Worker<T>::test(const T& t1)
{
	string str = "worker::test is Running!";
	::Display<char>(str);
	return t1;
}
#endif
CPP檔案也採用預編譯命令,防止其重複編譯。這樣問題就解決了。

也許,你以為就到此結束。編譯問題似乎我們完全解決了。但是,另一種情況下,假若我們的模板函式包含一個模板的特化版本,採用這種策略,編譯器仍然還是會報錯。下面我們來看下面的程式碼:

//在func.h 中我們聲明瞭幾個模板函式(採用如上所述的策略)

#ifndef FUNC_H
#define FUNC_H
#include <iostream>
#include<vector>
#include<iterator>
#include<algorithm>
using namespace std;

template<typename ch> void Display(string str); 
template<typename Type> void Work(Type t1);
template<>void Work<int>( int t1);//Work 的特化版本

#include"func.cpp"
#endif
//func.cpp實現如下
#ifndef FUNC_CPP
#define FUNC_CPP

#include "func.h"

template<typename ch>
void Display(string str)
{
	ostream_iterator<char>out_it(cout, "");
	copy(str.begin(), str.end(), out_it);
	*out_it = '\n';
};

template<typename Type>
void Work(Type t1)
{
	string txt = "Work func nomal is runing !";
	Display<char>(txt);
}


template<typename Type,int Num>
Type Sum(const Type& t1, int Num)
{
	string txt = "The sum of t1+Num is: ";
	
	Type tp = t1+Num;
	Display<char>(txt);
	cout<<tp<<endl;
	return tp;
}

template<>
void Work<int>( int t1)
{
	string txt = "now Work is special version.";
	Display<char>(txt);
}
#endif

在另一個CPP檔案中

void main()
{
          typedef void (*PTEM)(double t1);//定義一個指標呼叫一個,非特化版本的模板函式
	    PTEM pTem = Work;
	    pTem(3232.0);
	    Work<int>(323)//呼叫特化版本的模板函式
}
看起來沒有什麼問題吧,但是你編譯一下,依然報錯。不是麼?

error LNK2005: "void __cdecl Work<int>(int)" ([email protected]@@[email protected]) already defined in func.obj

又是重定義!!我們明明都用了#ifndef  這一套預編譯指令了的啊,為什麼在生成目標檔案的時候,還是重定義了。而且只是說特化版本重定義了。其中具體的原因在於特化版本的編譯機制,在這裡我不想多說,因為本來這個機制比較複雜。我不能在自己都還不是完全理解的情況下,在這裡班門弄斧。所以這裡只說說我自己的兩種處理方法 :

1.依然採用包含編譯方法,這種方法最簡單。只需要將特化版本的函式,宣告稱inline函式 即可(其他的不變)。

//標頭檔案中
template<> inline void Work<int>( int t1);
//cpp檔案中
template<>
inline void Work<int>( int t1)
{
	string txt = "now Work is special version.";
	Display<char>(txt);
}

記住啊這裡一定要加inline!

2.拋棄包含編譯,採用以前笨辦法,將基本模板函式的實現全部放在標頭檔案中,只在標頭檔案中宣告特化版本的函式。在CPP檔案中只實現特化版本的函式。

#ifndef FUNC_H
#define FUNC_H
#include <iostream>
#include<vector>
#include<iterator>
#include<algorithm>
using namespace std;
/////////////////////////標頭檔案中實現基本模板函式////////////////////////
template<typename ch>
void Display(string str)
{
	ostream_iterator<char>out_it(cout, "");
	copy(str.begin(), str.end(), out_it);
	*out_it = '\n';
};

template<typename Type>
void Work(Type t1)
{
	string txt = "Work func nomal is runing !";
	Display<char>(txt);
}
template<> void Work<int>( int t1);//特化版本的宣告

#endif

////////////////////CPP檔案中只實現特化版本的函式////////////////
#include "func.h"
#ifndef FUNC_CPP
#define FUNC_CPP
template<>
void Work<int>( int t1)
{
	string txt = "now Work is special version.";
	Display<char>(txt);
}

#endif

這樣也能通過編譯,也許你覺得第二個辦法不高明。轉來轉去又回到了原點,所以要是不喜歡的話,還是推薦用第一種方法。

綜上所述,我個人覺得,包含編譯在模板程式中的確是首選,他很簡單方便。(分別編譯暫且不談,因為不是每一個編譯器都支援這種方式。)但是採用包含要注意有些特殊情況,如我上面例舉出的例子。

相關推薦

深入探討vcC++模板編譯模型

寫過模板的朋友也許知道,一個模板程式,當編譯器看到模板定義時並不立即產生程式碼,只有在我們用到模板,並對其例項化的時候,才會產生特定的例項。此時,編譯器就要訪問定義模板的原始碼了。如果原始碼不可訪問,當然,編譯器會報錯的。記得我初學的時候,採用的是直接將宣告和實現全部放在一

C++模板編譯模型

pre str 查找 enc 生成可執行文件 turn 對象 endif 使用 一:傳統的編譯模型 使用C/C++進行編程時,一般會使用頭文件以使定義和聲明分離,並使得程序以模塊方式組織。將函數聲明、類的定義放在頭文件中,而將函數實現以及類成員函數的定義放

C++模板編譯模型:包含編譯模型

    在包含編譯模型中,編譯器必須看到用到的所有模板的定義。一般而言,可以通過在宣告模板函式或類模板的標頭檔案中新增一條#include指示使定義可用,該#include引入了包含相關定義的原始檔。 1.header file  tt.h #ifndef TT_H #def

LinuxC語言編譯的問題

 在Linux下程式設計發現一個詭異的現象,就是在連結一個靜態庫的時候總是報錯,類似下面這樣的錯誤:  (.text+0x13): undefined reference to `func'  關於undefined reference這樣的問題,大家其實經常會遇到,在此,我以詳細地示例給出常見錯誤的各

透徹理解c++模板包含模型(轉)

原文的地址是:http://blog.csdn.net/ixsea/article/details/6695496 觀點 包含模型是C++模板原始碼的一種組織方式,它鼓勵將模板程式碼全部放在一個.h標頭檔案中,這樣可以避免莫名其妙的連結錯誤。 莫名其妙的連

linuxC語言編譯報錯gets函式警告

linux C語言程式設計錯誤解決之 “warning: the `gets' function is dangerous and should not be used.” 問題出在程式中使用了 gets Linux 下gcc編譯器不支援這個函式,解決辦法是使用 

linuxC語言編譯為彙編程式碼

1 .file "s1.c" 2 .text 3 .globl fun 4 .type fun, @function 5 fun: 6 pushl %ebp 7 movl %esp, %ebp 8 popl %ebp 9

LinuxC語言編譯基礎及makefile的編寫

這篇文章介紹在LINUX下進行C語言程式設計所需要的基礎知識。在這篇文章當中,我們將會學到以下內容: 源程式編譯 Makefile的編寫 程式庫的連結 程式的除錯 標頭檔案和系統求助 1.源程式的編譯 在Linux下面,如果要編譯一個C語言源程式,我們要使用GNU的gc

C/C++ 語句{__asm int 3;} 能在VC編譯的過麼?

今天在寫一個巨集時始終報 fatal error C1075: end of file found before the left brace '{' at 'd:\xxx.cpp(49)' was matched 檢查排除程式碼,去除了巨集,留下了一句 {__asm int 3;} 將大括號去掉,

linuxC++引用模板類成員,編譯出錯

C++引用模板類成員,編譯出錯,錯誤類似於-》missing 'typename' prior to dependent type name 'SeqList<Type>::referen

深入理解計算機系統》關於csapp.h和csapp.c編譯問題(轉)

系統 文件中 class net 工作 inux 而且 pan div 編譯步驟如下: 1.我的當前工作目錄為/home/sxh2/clinux,目錄下有3個文件inet_aton.c csapp.h csapp.c。 2.編譯csapp.c文件,命令為gcc -c csa

linux使用gcc編譯運行C程序

images 鏈接 cnblogs bin collect 運行 ffffff alt 兩種 gcc(GNU Compiler Collection)是Linux下最常用的C語言編譯器,是GNU項目中符合ANSI C標準的編譯系統,能夠編譯用C、C++和Object C等語

vscode基於Linux和Windowsc/c++的多文件編譯與連接

vscode c/c++ 有時寫寫小程序,又不想啟動2013,vscode就成了我的首選。剛接觸vscode的時候,跟著網上配置了一堆東西,總算能編譯C/C++了,但一涉及到多文件,我還得乖乖的打開vs2013。前些天在配置Linux上的vscode的時候,突然發現有網友在tasks.json

LinuxC語言程序的編譯過程與ssc平臺出租

vim 匯編 實例 \n sem urn 結果 linux return 使用gcc編譯程序時,編譯工程分為4個階段:ssc平臺出租(企 娥:217 1793 408) (1)預處理:(Pre-Processing) (2)編譯:(Compiling) (3)匯編:(Ass

windowsMongodb的C++驅動編譯

ODB 2.7 stat 安裝 scons 源碼 org bsp article 安裝說明:   https://github.com/mongodb/mongo-cxx-driver/wiki/Download-and-Compile-the-Legacy-Driver

Ubuntu怎麽編譯並運行CC++和Pascal語言?

tro 開始 用戶 步驟 ide gcc 但是 col inf 很多同學在安裝了Ubuntu的環境後,發現在Windows下的許多東西都打不開了,但是用網站上的在線IDE又不是很方便。 所以,ljn教你如何在Ubuntu下編譯並運行C、C++和Pascal。 一.編譯並

Linux環境c程序的編譯和執行

環境變量 動態 main.c tor direct 環境 沒有 stdlib.h share 1 單個文件的編譯和執行創建main.c文件,內容如下: #include <stdio.h> #include <stdlib.h> int main

Linux環境c程式的編譯和執行

1 單個檔案的編譯和執行建立main.c檔案,內容如下: #include <stdio.h> #include <stdlib.h> int main(void){ printf("Hello world!\n"); return 0; };   編譯:

c++ 入門之深入探討拷貝函式和賦值運算子

在c++入門之深入探討類的一些行為時,說明了拷貝函式即複製建構函式運用於如下場景: 物件作為函式的引數,以值傳遞的方式傳給函式。  物件作為函式的返回值,以值的方式從函式返回 使用一個物件給另一個物件初始化 針對上述的三種情況,實際上很多時候,我們都會用到;如果我們採用系統預設的拷貝函式

Ubuntu編輯並編譯執行c++程式

一、使用vim編輯c++程式碼: vim hello.cpp 輸入如下程式碼: #include <iostream> using namespace std; int main() { cout<<"Hello world!"<<endl;