C++模板机制总结

模板是C++中非常重要的组成部分,之前自己对这块领域一直不太熟悉。最近趁着有时间学习了一下,特此总结。

首先是函数模板,它的定义方式如例子所示:

template <typename T>T sum(T a,T b)
{
        return a+b;
}

类也有自己的模板,称为类模板,如下所示:

template <typename T>class Proxy
{
public:
        typedef T value_type;
        Proxy();
        ~Proxy();
        value_type val;
};

template<typename T> Proxy<T>::Proxy()
{
        val=T(10);
}

template<typename T> Proxy<T>::~Proxy()
{

}

 要特别注意的是,C++中类模板的声明和定义应当放到同一个.h文件下,不能将声明和定义分散在不同的文件中。

类模板也可以有自己的友元函数和静态变量:

template <typename>class Proxy;
template <typename T>bool operator==(const Proxy<T>&,const Proxy<T>&);
template <typename T>class Proxy
{
public:
        typedef T value_type;
        Proxy();
        ~Proxy();
        void printval();
        friend bool operator==<T>(const Proxy<T>&,const Proxy<T>&);
        value_type val;
        static T abc;
};
template<typename T> void Proxy<T>::printval()
{
        std::cout<<val<<std::endl;
}
template <typename T> T Proxy<T>::abc=0;

无论是模板类还是普通类,都可以有自己的模板函数,编译器会根据具体代码的内容实例化特定的模板成员函数。

template <typename T>class Proxy
{
public:
        template<typename U>void printNum(U num);
};

template<typename T>template<typename U>void Proxy<T>::printNum(U num)
{
        std::cout<<num<<std::endl;
}

  当我们引用模板函数的时候,编译器利用调用中的函数实参来确定其模板参数,这叫做模板实参推断。但只有有限的几种类型转换会自动地应用于这些实参(const转换、数组或函数指针转换)。拿一段代码来举个例子:

template <typename T>T sum(T a,T b)
{
        return a+b;
}

sum(10,10.5);//this code is error!

在引用sum的时候,实参一个是int类型,一个是double类型,此时不能像普通函数一样实现类型转换。以上代码在编译器是不通过的。

而如果先将函数显示实例化,则就可以实现参数类型转换了:

sum<int>(10,10.5);

当我们不能(或不希望)将模板定义用于某些特定类型时,模板特例化就派上用场了。模板特例化就是一个用户提供的模板实例,它将一个或多个模板参数绑定到特定类型或值上。比如之前定义的sum函数,可以针对Sale_Data类特例化:

template<>
Sale_Data sum(Sale_Data a,Sale_Data b)
{
    Sale_Data c;
    c.index=a.index+b.index;
    return c;
}       

这样一来,当我们为Sale_Data调用sum时,编译器就会采用上面这个函数;而不再使用通用的模板函数。

另外还需要记住typename的使用,如下面例子所示:

template <typename T>class A
{
public:
    struct AS{};     
};

void test()
{
    typename A<int>::AS as;  
}

此时在test函数中,如果我们不加typename,那么编译器无法确认AS到底A中的一种类型还是一种静态变量,因此需要用这种方式来声明。

例外情况如下所示:

(1)类模板定义中的基类列表

template<class T>
class Derived: public Base<T>::XXX
{
...
}

(2)类模板定义中的初始化列表

Derived(int x) : Base<T>::xxx(x)
{ 
...
}

为什么这里不需要呢?因为编译器知道这里需要的是类型还是变量,(1)基类列表里肯定是类型名,(2)初始化列表里肯定是成员变量名。