模板 (C++)在C++程序设计语言中,模板(Template)是指函数模板与类模板[1],是一种参数化类型机制。Java和C#中的泛型与C++的模板大体对应,但也有一些功能上的显著差异(C++模板支持两者没有明确对应的模板模板参数和模板非类型参数,但不支持Java的通配符以及C#的泛型类型约束)。模板是C++的泛型编程中不可缺少的一部分。 模板是C++程序员绝佳的武器,特別是結合了多重继承与运算符重载之后。C++的标准函数库提供的许多有用的函数大多結合了模板的概念,如STL以及iostream。 语法模板的声明与定义模板定义以关键字 模板的非类型形参模板的非类型形参(template non-type parameter)允许为下述形式:
模板的非类型参数被声明为数组或函数的,将被转换为指针或函数指针。例如: template<int a[4]> struct A { }; template<int f(int)> struct B { }; int i; int g(int) { return 0;} A<&i> x; B<&g> y; 模板的非类型形参允许用const或volatile限定(而模板的类型形参是不允许cv限定的)。模板的非类型形参是不允许声明为浮点型、class类型、void型。 模板的模板参数类模板的模板参数允许是另外一个类模板,这称为模板的模板参数(template template parameter),也译作“模板参数模板”。函数模板不允许有模板的模板参数。例如: template<template <class T> class X> class A { }; //类模板A的第二个参数是另外一个类模板X template<class T> class B { }; A<B> a; //模板A的实际使用。其中的B是模板的模板实参(template template argument) 模板参数的默认值模板形参可以给出默认值(default arguments for template parameters)。如果一个模板参数给出了默认值,那么模板形参列表中在其后声明的模板参数都应该给出默认值。例如: template<class T = char, class U, class V = int> class X { }; //编译出错,或者给出U的默认值,或者不给出T的默认值 一个模板的各次声明给出的模板参数的默认值可以累积其效果。例如: template<class T, class U = int> class A; template<class T = float, class U> class A; template<class T, class U> class A { public: T x; U y; }; A<> a; //a.x is float, and the type of a.y is int 但是如果交换本示例第一行与第二行的次序,将编译报错。因为如果第一个模板参数T有了默认值,此时编译器必须已经知道其后的第二个模板参数U的默认值。 在同一个作用域(scope)中,不能对同一个模板的同一个参数多次声明其默认值。例如: template<class T = char> class X; template<class T = char> class X { };//编译报错。如果在本行中不给出模板参数T的默认值将编译通过 模板参数的作用域为从其声明之处至该模板的定义结束之处。因此可以使用一个模板参数作为其后声明的其他模板参数的一部分或默认值。例如: template<class V, V obj> class C; template<class T, class U = T> class D { }; 变量模板变量模板(variable template)是C++14引入的新的一个种类的模板。可用于在命名空间作用域声明一个变量。例如: template<class T> constexpr T pi = T(3.1415926535897932385); // variable template template<class T> T circular_area(T r) // function template { return pi<T> * r * r; // pi<T> is a variable template instantiation } 可以在类作用域声明一个静态数据成员: struct matrix_constants { template<class T> using pauli = hermitian_matrix<T, 2>; // alias template template<class T> static constexpr pauli<T> sigma1 = { { 0, 1 }, { 1, 0 } }; // static data member template template<class T> static constexpr pauli<T> sigma2 = { { 0, -1i }, { 1i, 0 } }; template<class T> static constexpr pauli<T> sigma3 = { { 1, 0 }, { 0, -1 } }; }; 类的静态数据成员模板,也可以用类模板的非模板数据成员来实现: struct limits { template<typename T> static const T min; // declaration of a static data member template }; template<typename T> const T limits::min = { }; // definition of a static data member template template<class T> class X { static T s; // declaration of a non-template static data member of a class template }; template<class T> T X<T>::s = 0; // definition of a non-template data member of a class template 变量模板不能用作模板的模板参数(template template arguments)。 模板的使用使用模板时,可以在模板名字后面显式给出用尖括号括住的模板实参列表(template argument list)。对模板函数或类的模板成员函数,也可不显式给出模板实参,而是由编译器根据函数调用的上下文推导出模板实参,这称为模板参数推导。 如果模板参数使用其默认值,则在模板实参列表中可以忽略它。如果所有的模板参数都使用了默认值,模板实参列表为空,但仍然必须写出成对的尖括号。例如: template<class T = int> class X { }; X<> a; //编译通过 X b; //编译报错 对于作为类型的模板实参,不允许是局部类型(local type)、无链接性的类型(type with no linkage)、无名类型(unnamed type)或包括了这三种情形的复合类型。[2]但C++11以及允许本地类型作为模板实参。 示例函数模板以下以取最大值的函数模板maximum为例。此函数在编译时会自动产生对应参数类型的代码,而不用显式声明。 #include <iostream>
template <typename T>
inline const T& maximum(const T& x,const T& y)
{
if(y > x){
return y;
}
else{
return x;
}
}
int main(void)
{
using namespace std;
int a=3,b=7;
float x=3.0,y=7.0;
//Calling template function
std::cout << maximum<int>(a,b) << std::endl; //输出 7
std::cout << maximum(a, b) << std::endl; //自动补充类型声明
std::cout << maximum<double>(x,y) << std::endl; //输出 7
return 0;
}
类模板
#pragma once
template <typename Ty>
class ComPtr
{
protected:
Ty* m_ptr;
public:
ComPtr()
{
m_ptr = NULL;
}
ComPtr(const ComPtr& rhs)
{
m_ptr = NULL;
SetComPtr(rhs.m_ptr);
}
ComPtr(Ty* p)
{
m_ptr = NULL;
SetComPtr(p);
}
~ComPtr()
{
Release();
}
const ComPtr& operator=(const ComPtr& rhs)
{
SetComPtr(rhs.m_ptr);
return *this;
}
Ty* operator=(Ty* p)
{
SetComPtr(p);
return p;
}
operator Ty* ()
{
return m_ptr;
}
Ty* operator->()
{
return m_ptr;
}
operator Ty** ()
{
Release();
return &m_ptr;
}
operator void** ()
{
Release();
return (void**)&m_ptr;
}
bool IsEmpty()
{
return (m_ptr == NULL);
}
void SetComPtr(Ty* p)
{
Release();
m_ptr = p;
if (m_ptr)
{
m_ptr->AddRef();
}
}
void Release()
{
if (m_ptr)
{
m_ptr->Release();
m_ptr = NULL;
}
}
};
模板的嵌套:成员模板对于类中的模板成员函数、嵌套的成员类模板,可以在封闭类的内部或外部定义它们。当模板成员函数、嵌套类模板在其封闭类的外部定义时,必须以封闭类模板的模板参数(如果它们也是模板类)和成员模板的模板参数开头。[1]如下例: template <typename C> class myc{
public:
template <typename S> C foo(S s);
};
//下行需要给出外部类与内部嵌套类的模板形参列表:
template<typename C> template <typename S> C myc<C>::foo(S s){
C var;
return var;
}
int main()
{
float f;
myc<int> v1;
v1.foo(f);
}
C++标准规定:如果外围的类模板没有特例化,里面的成员模板就不能特例化[3]。例如: template <class T1> class A {
template<class T2> class B {
template<class T3> void mf1(T3);
void mf2();
};
};
template <> template <class X>
class A<int>::B {
template <class T> void mf1(T);
};
template <> template <> template<class T>
void A<int>::B<double>::mf1(T t) { }
template <class Y> template <>
void A<Y>::B<double>::mf2() { } // ill-formed; B<double> is specialized but its enclosing class template A is not
依赖名字与typename关键字一个模板中的依赖于一个模板参数(template parameter)的名字被称为依赖名字 (dependent name)。当一个依赖名字嵌套在一个类的内部时,称为嵌套依赖名字(nested dependent name)。一个不依赖于任何模板参数的名字,称为非依赖名字(non-dependent name)。[4] 编译器在处理模板定义时,可能并不确定依赖名字表示一个类型,还是嵌套类的成员,还是类的静态成员。C++标准规定:如果解析器在一个模板中遇到一个嵌套依赖名字,它假定那个名字不是一个类型,除非显式用typename关键字前置修饰该名字。[5] typename关键字有两个用途:
在下述情形,对嵌套依赖类型名字不需要前置修饰typename关键字:[6]
因为它们的上下文已经指出这些标识符就是作为类型的名字。例如: template <class T> class A: public T::Nested { //基类列表中的T::Nested
public:
A(int x) : T::Nested(x) {}; //成员初始化列表中的T::Nested
struct T::type1 m; //已经有了struct关键字的T::type1
};
class B{
public:
class Nested{
public:
Nested(int x){};
};
typedef struct {int x;} type1;
};
int main()
{
A<B> a(101);
return 0;
}
template关键字template关键字有两个用途:
class A { public:
template <class U> class B{
public: typedef int INT;
};
template <class V> void foo(){}
};
template <typename T>
int f()
{
typename T::template B<double>::INT i;
i=101;
T a, *p=&a;
a.template foo<char>();
p->template foo<long>();
return 0;
}
int main()
{
f<A>();
A::B<double>::INT i; // 自C++11起,也可写作typename A::template B<double>::INT i;
}
别名模板别名模板(aliase template)是C++11引入的技术。在C++03标准中,可以用typedef给全特化模板定义新的类型名。但是不允许用typedef施加于偏特化模板上。例如: template <typename First, typename Second, int Third>
class SomeType;
template <typename Second>
typedef SomeType<OtherType, Second, 5> TypedefName; // Illegal in C++03
C++11增加了给偏特化模板增加别名的功能,例如: template <typename First, typename Second, int Third>
class SomeType;
template <typename Second>
using TypedefName = SomeType<OtherType, Second, 5>;
typedef void (*FunctionType)(double); // Old style
using FunctionType1 = void (*)(double); // New introduced syntax
模板實例化模板实例化(template instantiation)是指在编译或链接时生成函数模板或类模板的具体实例源代码。ISO C++定义了两种模板实例化方法:隐式实例化(当使用实例化的模板时自动地在当前代码单元之前插入模板的实例化代码)、显式实例化(直接声明模板实例化)。在C++语言的不同实现中,模板编译模式(模板初始化的方法)大致可分为三种:
ISO C++标准规定,如果隐式实例化模板,则模板的成员函数一直到引用时才被实例化;如果显式实例化模板,则模板所有成员立即都被实例化,所以模板的声明与定义在此处都应该是可见的,而且在其它程序文本文件使用了这个模板实例时用编译器选项抑制模板隐式实例化,或者模板的定义部分是不可见的,或者使用template<> type FUN_NAME(type list)的语句声明模板的特化但不实例化。
VC++7.0中必须类模板实例化只有Borland模型;函数模板一般隐式实例化,自5.0版以后也可显式实例化。 参考文献
|
Portal di Ensiklopedia Dunia