c++模板函数

模板特化(也有翻译为模板具体化)(specialization)

  如果把模板函数当作数学归纳法的话,模板特化就是n=常数C的情况。

//模板函数声明
template <typename T>
bool greater(const T &a,const T &b);
int main()
{
    return 0;
}
//模板函数定义
template <typename T>
bool greater(const T &a,const T &b)
{
    if(a > b)
        return true;
    else
        return false;
}

  如果上面的T是char*类型,那么这种比较是不符合我们要求的,它比较的是地址,char*应该用strcmp。

  如果按照我们最简单的思路的话就是直接将T换成char*,但这样编译却给出了error,说这个特化找不到对应的模板函数。

#include <cstring>
//模板函数声明
template <typename T>
bool greater(const T &a,const T &b);
//特化的模板函数的声明
template<>
bool greater<char*>(const char * &a,const char * &b);
int main()
{
    return 0;
}
//模板函数定义
template <typename T>
bool greater(const T &a,const T &b)
{
    if(a > b)
        return true;
    else
        return false;
}
//特化的模板函数的定义
template<>
bool greater<char *>(const char * &a,const char * &b)
{
    return strcmp(a,b)>0?true:false;
}

  我们模板函数的定义const T &a,其实也可以是这种写法T const &a,这两种写法表明const和T分别修饰a,也就是a的类型既是const又是T;而不是const T作为一个整体,a的类型是const T。虽然在这个问题上,这两种理解没有出现语义的误解,但是在上面的例子就出现误解了。

根据我们原来的模板函数的定义,我们的目的应该是这样的特化函数的参数,`const (char *) &a`(是没有这种写法,只是为了让我们理解方便),也就是说char  \*是一个整体,和const一起修饰a。`const char* &a`却让`const char`作为了一个类型,这显然不是我们要的。`char* const &a`这才是我们想要的。
#include <cstring>
//模板函数声明
template <typename T>
bool greater(T const &a,T const &b);
//特化的模板函数的声明
template<>
bool greater<char*>(char * const &a,char * const &b);
int main()
{
    return 0;
}
//模板函数定义
template <typename T>
bool greater(T const &a,T const &b)
{
    if(a > b)
        return true;
    else
        return false;
}
//特化的模板函数的定义
template<>
bool greater<char*>(char * const &a,char * const &b)
{
    return strcmp(a,b)>0?true:false;
}

  如果在写类型时,习惯将const放在类型char等后面,那么写模板特化时,直接代换T就不会出现错误。

定义与实现分离?

  在传统上,我们总是把定义写在.h文件里,而实现文件写在.cpp文件里,但这在模板里面是否可以,请看下面一个例子。

//swap.h
template <typename T>
void swap(T &a,T &b);
//swap.cpp
template <typename T>
void swap(T &a,T &b)
{
    T temp = a;
    a = b;
    b = temp;
}
//main.cpp
#include "swap.h"
int main()
{
    int a,b;
    swap(a,b);
    //...
    return 0;
}

  编译器在编译main.cpp文件时只看到swap的声明而没有定义(实现),所以留空,让连接器去链接swap的实现。而编译器在编译swap.cpp时没有看到模板参数,所以也不会编译,最后便会导致链接错误,即找不到swap<int>。

  所以模板函数或者模板类,应该将定义和实现放在.h文件。

显示实例化(explict initialization)

  实例化:一个通过使用具体值替换模板参数,从模板产生的普通类,函数或者成员函数的过程。
  隐式实例化:这是编译器看到模板函数时,在当前文件实现相应的模板参数的实例化。
  显示实例化:就是自己手工让编译器在此文件实现相应的模板参数的实例化。
既然编译器会自动实现实例化,为什么还要我们去手工去让编译器实现实例化呢?请看下面的例子。

//swap.h
template <typename T>
void swap(T &a,T &b)
{
    T temp = a;
    a = b;
    b = temp;
}
//function.h
void function();
//function.cpp
#include "swap.h"
void function()
{
	int a,b;
	swap(a,b);
}
//main.cpp
#include "swap.h"
#include "function.h"
int main()
{
    int a,b;
    swap(a,b);
	function();
    //...
    return 0;
}

  为了容易看出,我们暂且用function表示一个用到swap的函数,它也可以是一个类。
  编译器在编译function.cpp时会用void swap<int>(int &a,int &b),故生成的obj文件有void swap<int>(int &a,int &b)的二进制代码,同理,在main.cpp生成的obj文件里也有void swap<int>(int &a,int &b)的二进制代码。也就是说最后生成的exe有两个相同的二进制代码。
  为了减少代码,我们可以用一个cpp文件(就起名为explict_initilization.cpp)手动显示实例化这些模板类,如下。这样的话,链接器最后会将main.cpp和function.cpp中的swap<int>链接到explict_initilization.cpp。

//swap.h
template <typename T>
void swap(T &a,T &b);
//swap
templatee <typename T>
void swap(T &a,T &b)
{
    T temp = a;
    a = b;
    b = temp;
}
//explict_initilization.cpp
#include "swap.cpp"
template void swap<int>(int&,int&);//显示实例化
//function.h
void function();
//function.cpp
#include "swap.h"
void function()
{
	int a,b;
	swap(a,b);
}
//main.cpp
#include "swap.h"
#include "function.h"
int main()
{
    int a,b;
    swap(a,b);
	function();
    //...
    return 0;
}

更加详细的介绍可以看c++模板类(一)理解编译器的编译模板过程

export的用法

  为了解决上面用一个explict_initilization.cpp来管理实例化文件的不便利,c++可以export这个关键词,让编译器和链接器去管理我们上面要解决的事情。

//swap.h
export
template <typename T>
void swap(T &a,T &b);
//swap
templatee <typename T>
void swap(T &a,T &b)
{
    T temp = a;
    a = b;
    b = temp;
}
//function.h
void function();
//function.cpp
#include "swap.h"
void function()
{
	int a,b;
	swap(a,b);
}
//main.cpp
#include "swap.h"
#include "function.h"
int main()
{
    int a,b;
    swap(a,b);
	function();
    //...
    return 0;
}

通过export这个关键字,即使在模板定义不可见的条件下,被导出的模板也可以正常使用,当编译器看到export这个关键字,会自动去搜寻模板的定义。
这个看似很美好,但是在export这个关键字推出到2011年,很少有编译器去实现这个关键字的功能,所以在C++11到C++20这段期间被作为了一个保留字,C++20又被赋予了新的意义。

posted @ 2018-04-06 22:03  h_hg  阅读(295)  评论(0编辑  收藏  举报