博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
模板(Template)
阅读量:6251 次
发布时间:2019-06-22

本文共 11287 字,大约阅读时间需要 37 分钟。

最近阅读google chromium base container stack_container代码,深刻感觉到基础知识不扎实。

// Casts the buffer in its right type.

T* stack_buffer() { return stack_buffer_.template data_as<T>(); }
const T* stack_buffer() const {
  return stack_buffer_.template data_as<T>();
}

关于.template的用法,维基百科关于Template描述中有详细说明

模板Template)指中的模板与模板,是一种机制,大体对应于和中的,但也有一些功能上的显著差异(C++模板支持后两者没有明确对应的模板模板参数和模板非类型参数,但不支持Java的通配符以及C#的泛型类型约束)。模板是C++的中不可缺少的一部分。

模板是C++程序员绝佳的武器,特别是结合了与之后。C++的标准函数库提供的许多有用的函数大多结合了模板的概念,如以及。

目录

语法

模板的声明与定义

模板定义以关键字template开始,后接模板形参表(template parameter list),模板形参表是用尖括号括住的一个或者多个模板形参的列表,形参之间以逗号分隔。模板形参可以是表示类型的类型形参(type parameter),也可以是表示常量表达式的非类型形参(non-type parameter)。非类型形参跟在类型说明符之后声明。类型形参跟在关键字class或typename之后声明。模板形参可以给出默认值(default arguments for template parameters)。

模板的非类型形参

模板的非类型形参(template non-type parameter)允许为下述形式:

  • 整型或枚举型
  • 到对象的指针或函数指针
  • 到对象的引用或函数引用

模板的非类型参数被声明为数组或函数的,将被转换为指针或函数指针。例如:

template
struct A { }; template
struct B { }; int i; int g(int) { return 0;} A<&i> x; B<&g> y;

模板的非类型形参允许用const或volatile限定(而模板的类型形参是不允许cv限定的)。模板的非类型形参是不允许声明为浮点型、class类型、void型。

模板的模板参数

类模板的模板参数允许是另外一个类模板,这称为模板的模板参数(template template parameter),也译作“模板参数模板”。函数模板不允许有模板的模板参数。例如:

template

模板参数的默认值

模板形参可以给出默认值(default arguments for template parameters)。如果一个模板参数给出了默认值,那么模板形参列表中在其后声明的模板参数都应该给出默认值。例如:

template
class X { }; //编译出错,或者给出U的默认值,或者不给出T的默认值

一个模板的各次声明给出的模板参数的默认值可以累积其效果。例如:

template
class A;template
class A;template
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 X;template
class X { };//编译报错。如果在本行中不给出模板参数T的默认值将编译通过

模板参数的作用域为从其声明之处至该模板的定义结束之处。因此可以使用一个模板参数作为其后声明的其他模板参数的一部分或默认值。例如:

template
class C; template
class D { };

变量模板

变量模板(variable template)是引入的新的一个种类的模板。可用于在命名空间作用域声明一个变量。例如:

template
constexpr T pi = T(3.1415926535897932385); // variable templatetemplate
T circular_area(T r) // function template{ return pi
* r * r; // pi
is a variable template instantiation}

可以在类作用域声明一个静态数据成员:

struct matrix_constants{   template
using pauli = hermitian_matrix
; // alias template template
static constexpr pauli
sigma1 = { { 0, 1 }, { 1, 0 } }; // static data member template template
static constexpr pauli
sigma2 = { { 0, -1i }, { 1i, 0 } }; template
static constexpr pauli
sigma3 = { { 1, 0 }, { 0, -1 } };

};

类的静态数据成员模板,也可以用类模板的非模板数据成员来实现:

struct limits {   template
static const T min; // declaration of a static data member template};template
const T limits::min = { }; // definition of a static data member templatetemplate
class X { static T s; // declaration of a non-template static data member of a class template};template
T X
::s = 0; // definition of a non-template data member of a class template

变量模板不能用作(template template arguments)。

模板的使用

使用模板时,可以在模板名字后面显式给出用尖括号括住的模板实参列表(template argument list)。对模板函数或类的模板成员函数,也可不显式给出模板实参,而是由编译器根据函数调用的上下文推导出模板实参,这称为

如果模板参数使用其默认值,则在模板实参列表中可以忽略它。如果所有的模板参数都使用了默认值,模板实参列表为空,但仍然必须写出成对的尖括号。例如:

template
class X { };X<> a; //编译通过X b; //编译报错

对于作为类型的模板实参,不允许是局部类型(local type)、无链接性的类型(type with no linkage)、无名类型(unnamed type)或包括了这三种情形的复合类型。但C++11以及允许本地类型作为模板实参。

示例

函数模板

以下以取最大值的函数模板maximum为例。此函数在编译时会自动产生对应参数类型的代码,而不用显式声明。

#include 
template
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
(a,b) << std::endl; //输出 7 std::cout << maximum(a, b) << std::endl; //自动补充类型声明 std::cout << maximum
(x,y) << std::endl; //输出 7 return 0;}

类模板

以下以将组件指针的操作,封装成类别模板ComPtr为例。
#pragma oncetemplate 
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; } }};

模板的嵌套:成员模板

对于类中的模板成员函数、嵌套的成员类模板,可以在封闭类的内部或外部定义它们。当模板成员函数、嵌套类模板在其封闭类的外部定义时,必须以封闭类模板的模板参数(如果它们也是模板类)和成员模板的模板参数开头。如下例:

template 
class myc{
public: template
C foo(S s);};//下行需要给出外部类与内部嵌套类的模板形参列表:template
template
C myc
::foo(S s){
C var;return var; }int main(){
float f;myc
v1;v1.foo(f);}

C++标准规定:如果外围的类模板没有特例化,里面的成员模板就不能特例化。例如:

template 
class A {
template
class B {
template
void mf1(T3); void mf2(); };};template <> template
class A
::B {
template
void mf1(T); };template <> template <> template
void A
::B
::mf1(T t) { }template
template <> void A
::B
::mf2() { } // ill-formed; B
is specialized but its enclosing class template A is not

依赖名字与typename关键字

一个模板中的依赖于一个模板参数(template parameter)的名字被称为依赖名字 (dependent name)。当一个依赖名字嵌套在一个类的内部时,称为嵌套依赖名字(nested dependent name)。一个不依赖于任何模板参数的名字,称为非依赖名字(non-dependent name)。

编译器在处理模板定义时,可能并不确定依赖名字表示一个类型,还是嵌套类的成员,还是类的静态成员。C++标准规定:如果解析器在一个模板中遇到一个嵌套依赖名字,它假定那个名字不是一个类型,除非显式用typename关键字前置修饰该名字。

typename关键字有两个用途:

  1. 常见的在模板定义中的模板形参列表,表示一个模板参数是类型参数。等同于使用class
  2. 使用模板类内定义的嵌套依赖类型名字时,显式指明这个名字是一个类型名。否则,这个名字会被理解为模板类的静态成员名。起,这一用途也可以出现在模板以外,尽管此时typename关键字不是必要的。

在下述情形,对嵌套依赖类型名字不需要前置修饰typename关键字:

  • 派生类声明的基类列表中的基类标识符;
  • 成员初始化列表中的基类标识符;
  • classstructenum等关键字开始的类型标识符

因为它们的上下文已经指出这些标识符就是作为类型的名字。例如:

template 
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
a(101); return 0;}

template关键字

template关键字有两个用途:

  1. 常见的在模板定义的开始。
  2. 模板类内部定义了模板成员函数或者嵌套的成员模板类。在模板中,当引用这样的模板成员函数或嵌套的成员模板类时,可以在::(作用域解析)运算符、.(以对象方式访问成员)运算符、->(以指针方式访问成员)运算符之后使用template关键字,随后才是模板成员函数名字或嵌套的成员模板类名字,这使得随后的左尖括号<被解释为模板参数列表的开始,而不是小于号运算符。起,这一用途也可以出现在模板以外,尽管此时template关键字不是必要的。例如:
class A {
public: template
class B{
public: typedef int INT; }; template
void foo(){}};template
int f(){
i=101; T a, *p=&a; return 0;}int main(){
f
(); A::B
::INT i; // 自C++11起,也可写作typename A::template B
::INT i;}

别名模板

别名模板(aliase template)是C++11引入的技术。在C++03标准中,可以用typedef给全特化模板定义新的类型名。但是不允许用typedef施加于偏特化模板上。例如:

template 
class SomeType;template
typedef SomeType
TypedefName; // Illegal in C++03

C++11增加了给偏特化模板增加别名的功能,例如:

template 
class SomeType;template
using TypedefName = SomeType
;

using在C++11中也可用于其他的类型别名的声明:

typedef void (*FunctionType)(double);       // Old styleusing FunctionType1 = void (*)(double); // New introduced syntax

模板实例化

模板实例化template instantiation)是指在编译或链接时生成函数模板或类模板的具体实例源代码。ISO C++定义了两种模板实例化方法:隐式实例化(当使用实例化的模板时自动地在当前代码单元之前插入模板的实例化代码)、显式实例化(直接声明模板实例化)。在语言的不同实现中,模板编译模式(模板初始化的方法)大致可分为三种:

  • 模型(包含模板编译模式):生成每个中遇到的所有的模板实例,并存放在相应的中;合并相同的模板实例,生成。为了在每次模板实例化时模板的定义都是可见的,模板的声明与定义放在同一个.h文件中。这种方法的优点是只需要处理;这种方法的缺点是由于模板实例被重复编译,编译时间被加长了,而且不能使用系统的,需重新设计。
  • 模型(分离Separation)模板编译模式):的为解决模板实例化问题,增加了一个模板仓库,用以存放模板实例的代码并可被自动维护。当生成一个时,把遇到的模板定义与当前可生成的模板实例存放到模板仓库中。时,的包装程序(wrapper)首先调用生成所有需要的且不在模板仓库中的模板实例。这种方法的优点是速度得到了优化,而且可以直接使用系统的;这种方法的缺点是复杂度大大增加,更容易出错。使用这种模型的源程序通常把模板声明与非内联的模板成员分别放在.h文件与模板定义文件中,后者单独。
  • 混合(迭代)模型:目前是基于模型完成模板实例化。未来将实现混合模型的模板实例化,即把中的模板定义与遇到的当前可实现的模板实例存放在相应的中;的包装程序(wrapper)调用生成所需的目前还没有实例化的模板实例;合并所有相同的模板实例。使用这种模型的源程序通常把模板声明与非内联的模板成员分别放在.h文件与模板定义文件中,后者单独。

标准规定,如果隐式实例化模板,则模板的成员函数一直到引用时才被实例化;如果显式实例化模板,则模板所有成员立即都被实例化,所以模板的声明与定义在此处都应该是可见的,而且在其它程序使用了这个模板实例时用编译器选项抑制模板隐式实例化,或者模板的定义部分是不可见的,或者使用template<> type FUN_NAME(type list)的语句声明模板的特化但不实例化。

的模板实例化,目前分为三种方式:

  • 不指定任何特殊的参数:按模型写的源代码能正常完成模板实例化,但每个编译单元将包含所有它用到的模板实例,导致在大的程序中无法接受的代码冗余。需要用的删除各个中冗余的模板实例,不能使用操作系统提供的。
  • 使用-fno-implicit-templates编译选项:在生成时完全禁止隐式的模板实例化,所有模板实例都显式的写出来,可以存放在一个单独的源文件中;也可以存放在各个模板定义文件中。如果一个很大的源文件中使用了各个模板实例,这个源文件不用-fno-implicit-templates选项编译,就可以自动隐式的生成所需要的模板实例。在生成库文件时这个编译选项特别有用。
  • 使用-frepo编译选项:在生成每个时,把需要用到的当前可生成的模板实例存放在相应的.rpo文件中。包装程序(wrapper)—将删除.rpo文件中冗余的模板实例并且修改相应的.rpo文件,使得可以利用.rpo文件知道在那里正确放置、引用模板实例,并重新编译生成受影响的。由操作系统的通用的生成。这对模型是很好的模板实例化方法。对于使用模型的软件,需要修改源代码,在模板头文件的末尾加上#include <tmethods.cc>。不过中不包含包装程序,故不使用此方法。对于库(library),建议使用显式实例化方法。
  • 另外,扩展了ISO 标准,用extern指出模板实例在其它编译单元中显式声明(这已经被C++11标准接受);用inline实例化支持的数据(如的)但不实例化模板成员;用static实例化模板的但不实例化其它非静态的模板成员。
  • 不支持模板实例化的export(此关键字的这个用法已在C++11标准里被取消)。

7.0中必须类模板实例化只有模型;函数模板一般隐式实例化,自5.0版以后也可显式实例化。

参考文献

  1. ^
  2. §14.3.1/2 from the 2003 C++ Standard: A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template type-parameter.
  3. C++11标准:§14.7.3,¶16规定:the declaration shall not explicitly specialize a class member template if its enclosing class templates are not explicitly specialized as well
  4. C++11标准:§14.6,¶1
  5. C++11标准§14.6,¶2规定:A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename.
  6. C++11标准§14.6,¶5规定
  7. . [2014-09-27]. 

转载于:https://www.cnblogs.com/liaokang/p/5663227.html

你可能感兴趣的文章
Cacti+Nagios监控平台完美整合
查看>>
披星“戴”云,百治百效
查看>>
内存真实利用率
查看>>
由bean,及O/R映射文件导出数据库的方法ExportDB()
查看>>
利用Asp.net中的AJAX制作网页上自动选取开始日期及结束日期的用户自定义控件...
查看>>
python httplib post 进行表单提交数据
查看>>
2003加入域提示“用户已存在”
查看>>
Druid.io索引过程分析——时间窗,列存储,LSM树,充分利用内存,concise压缩
查看>>
Win2008 R2 VDI动手实验系列之四:远程桌面连接代理配置
查看>>
IT人的自我导向型学习:学习的4个层次
查看>>
基于Hadoop数据仓库Hive1.2部署及使用
查看>>
利用shell计算find命令查出后的总文件大小
查看>>
性能之外:LSI 6Gb/s SAS RAID渠道先行
查看>>
SCCM2012系列之十二,SCCM2012部署操作系统
查看>>
Docker镜像导致centos-root根分区容量爆满
查看>>
VB无所不能之七:VB的多线程(2)
查看>>
快速部署远程同步服务Rsync
查看>>
玩 High API 系列之:UGC内容检测
查看>>
Lync Server 2010边缘需要的公网IP数量
查看>>
开发常见错误解决(2)WSE3.0安装问题,VS2005集成
查看>>