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

寫過模板的朋友也許知道,一個模板程序,當編譯器看到模板定義時並不當即產生代碼,只有在咱們用到模板,並對其實例化的時候,纔會產生特定的實例。此時,編譯器就要訪問定義模板的源代碼了。若是源代碼不可訪問,固然,編譯器會報錯的。記得我初學的時候,採用的是直接將聲明和實現所有放在一個.h裏面這個方法。可是,有時候咱們確實想在.h文件中聲明,在CPP文件中實現,從而實現文件分離。那麼下面我就寫寫通常模板函數,模板類,模板特化的文件分離,我本身的心得。 ios

         在《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
  ......
}


這一策略,實現了頭文件和源文件的分離 spa

(2)分別編譯: 在分別編譯模式下,函數模板的聲明被放在頭文件中。在這種模式下,函數模板聲明和定義的組織方式與程序中的非內聯函數的聲明和定義組織方式相同。分別編譯模型 只是在使用了關鍵字"export"來告訴編譯器模板定義在哪裏引用. .net

若是在頭文件類聲明中使用了export,則該頭文件只能被源文件使用一次;若是在實現文件中使用了export,有下面兩種用法 指針

導出類 code

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

導出類成員函數,只用分別對成員使用export. blog

// XXXXX.H 文件中 只聲明
template <typename Type> Type max( Type t1, Type t2 );
// 在XXXXX.CPP 文件中
// 模板定義
export template <typename Type>
Type max( Type t1, Type t2 ) {/* . . . */}
  

看到這兒,你也許心花盛開,彷佛以爲如此簡單。好,因而你回家敲代碼,你會發現,你的編譯器VS照樣可能告訴你錯了! ci

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

      而後也許你採用的是第一種包含編譯,包含編譯是全部編譯器都支持的,可是你會發現,仍然有錯。咱們來看下面的一段代碼: 編譯器

在頭文件中

#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)" (??$Work@H @@YAXH@Z) already defined in func.obj

又是重定義!!咱們明明都用了#ifndef  這一套預編譯指令了的啊,爲何在生成目標文件的時候,仍是重定義了。並且只是說特化版本重定義了。其中具體的緣由在於特化版本的編譯機制,在這裏我不想多說,由於原本這個機制比較複雜。我不能在本身都還不是徹底理解的狀況下,在這裏班門弄斧。因此這裏只說說我本身的兩種處理方法 :

1.依然採用包含編譯方法,這種方法最簡單。只須要將特化版本的函數,聲明稱inline函數 便可(其餘的不變)。

<span style="font-size:16px;">//頭文件中
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);
}</span>

記住啊這裏必定要加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

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

綜上所述,我我的以爲,包含編譯在模板程序中的確是首選,他很簡單方便。(分別編譯暫且不談,由於不是每個編譯器都支持這種方式。)可是採用包含要注意有些特殊狀況,如我上面例舉出的例子。

轉自:http://blog.csdn.net/lh844386434/article/details/6713361
相關文章
相關標籤/搜索