C++模板——读书总结篇
函数模板
定义
-
例子一
template<typename T> void Func() { cout << "hello, world" << endl; }
-
例子二
template <typename T, template<typename,typename> class A, size_t N> void Func(T input) { T a = input; A<int , double> a; int array[N] = {}; }
实例化
-
隐式实例化
- 编译器使用函数实参来推断模板实参进行实例化。
- 对于返回值类型为模板参数的情况,编译器无法推断,必须显示指定。
- 当我们使用一个函数模板初始化一个函数指针时,编译器会使用函数指针的类型来推断模板实参的。如果不能唯一实例化(编译器推断不出来了),该操作就会失败。
-
显示实例化
当隐式无法实例化时,可以显示实例化。 例如:// 函数模板的声明与定义 template<typename RetType, typename T> RetType Func(T input) { RetType a = 1; return a; } // 函数模板的实例化 double a = 100.4; Func<int>(a);
类模板
模板的定义
-
举例一: 普通的定义
template<typename T> class Base { };
-
举例二:使用模板类作为类型参数
template<typename T> class Base { }; // 模板类作为类型形参数。 template<typename T, template<typename M> class Base> class Other { }
-
模板类的成员函数:对于类模板的成员函数,即可以在类内定义(默认为inline),也可以在类外定义(需要使用template关键字开始)。
// 类内定义 template<typename T> class A { public: void Print() { cout << "hello, world" << endl; } }; // 类外定义 template<typename T> class A { public: void Print(); } template<typename T> void A<T>::Print() { cout << "hello, owrld" << endl; }
-
类模板的类型名的简写: 在类模板自己的作用域时,可以直接使用类模板名,而不需要类模板名<类型参数> , 就是相当于简化了。例如:
template<typename T> class A { public: A<T>& GetInstance() { A* instance = new A; // A<T> 可以简写为A. return *instance; } };
类模板的实例化
- 默认情况下,一个类模板的成员函数只有在使用时,它才被实例化。所以呢, 有一些成员函数无法实例化时, 只要不使用它,就没有问题。
- 使用类模板时, 必须使用在<>中加上类型参数 进行显式实例化。
类模板与友元
- 当一个类包含一个友元时,类与友元各自是否为模板是相互无关的。如果一个类包含了一个非模板的友元,则友元被授权可以访问所有模板实例。 如果友元自己是模板,类可以授权给所有友元模板实例。
- 为了引用一个模板的特定实例,必须先声明模板自身。
- 在新标准中,可以将模板类型参数声明为友元。
template<typename type> class Bar { friend Type;};
模板类的别名
-
typedef 不允许为模板定义一个类型别名,因为模板不是一种类型。
-
使用using 可以为模板定义一个别名,例如:
template<typename T, typename M> using twin = pair<T, M>; template<typename T> using ttt = pair<T, int>; twin<int, char> a; ttt<double> b;
模板类内的类型成员
- 在模板内部可以定义一种类型,但是在使用的时候存在问题,编译器无法区别使用的类型还是类内的static 变量?例如 : T::new_type, 这个new_type到底是变量还是类型呢?
- 默认情况下, c++语言假定通过作用域运算符访问的名字不是类型
- 当我们使用类型时, 使用typename 来标识它,例如:
typename T::new_type a
(定义变量a)。
成员函数模板
-
一个类(无论是否为模板类),都只可以是模板的成员函数,这种成员叫作成员模板。
-
成员模板不可以是虚函数。
-
普通类的成员模板,即普通类的成员函数为模板函数,性质与函数模板相同。
-
类模板的成员模板:
a. 类与成员模板,它们有各自的独立的模板参数。
b. 当我们在类模板之外定义一个成员模板时,必须同时为类模板与成员模板提供参数列表。
c. 类模板的参数列表在前,成员模板的模板参数在后。// 类模板声明 template<typename T> class A { public: template<typename M> void Print(); }; // 成员函数的定义 template<typename T> template<typename M> void A<T>::Print() { cout << "hello" << endl; } // 使用 A<int> a; a.Print<double>();
别名模板
template<typename T>
using myVec = vector<T, allocator<T>>;
// 使用举例
template<template<typename T> class Continaer>
class Test {
};
// Test<vector> test1; // 编译报错
Test<myVec> test2; // OK
模板的参数
-
对于模板参数,即可以为类型,也可以为非类型参数,也可以为模板类。
-
当模板的参数为非类型参数时,
a. 它为一个值,而非类型,编译期可以确认的值。
b. 它只可以为整型、可以为对象或函数类型的指针或左值引用。(对象必须有静态生命周期,就是全局变量了。 如果是static 变量,需要是const的,原因是非const的static变量的地址编译器可能无法确定,因为的是文件作用域,我猜的)// 类型参数 template<typename T> void Print(); // 非类型参数 template<size_t M> void Print(); // 模板类参数 template <template<typename> class A> void Print(); // 指针或引用 template<int* p> void Print() { cout << 指针 << endl; } template<int& v> void Print() { cout << 引用 << endl; } int a = 10; int& b = a; Print<&a>(); Print<b>();
-
默认参数
a. 例如:template<typename T = int, typename Y = double>
b. 对于模板参数,只有当它右侧的所有参数都有默认实参时,它才可以有默认实参。
模板的实参的推断
-
指的是函数模板中根据实参类型进行推断形参。
-
对于函数模板来说,只有两种类型转换(隐式转换).除此之外,算术类型的转换、派生类到基类的转换、用户自定义的转换等都是不允许的。
a). const 转换,一个非const 对象的引用或指针的实参可以传递给一个const的引用或指针的形参。
b). 数组与函数指针转换:如果函数不是引用类型(当是引用类型时,数组的大小不一样,类型不同)时,可以对数组或函数类型的实参应用正常的指针转换。即数组转换为指针,函数转换为函数指针。 -
在函数模板中,如果使用到普通类型定义的参数, 它们是可以正常转换的。 只有 模板的类型参数不可以正常转换。
-
正常的类型转换也可以应用于显示指定的实参的。
-
当我们使用一个函数模板初始化一个函数指针时,编译器会使用函数指针的类型来推断模板实参的。如果不能唯一实例化(编译器推断不出来了),该操作就会失败。
-
重量级知识点:引用折叠与右值引用参数:
a): 当模板参数为左值引用时,只能传递给它的一个左值的实参。
b): 当模板参数为const 左值引用时,传递给它的一个左值的实参/临时对象、字面值等
c): 神奇到来:当模板函数参数是一个右值引用时,传递给它的实参可以是任意类型(也就是说左值也可以),原因就是因为引用折叠。
d): 例外规则一: 当我们将一个左值传给模板函数的右值引用参数(T&&)时, 编译器推断模板类型参数为的左值引用类型,例如为int类型时,推断T为int&.
e): 例外规则二:如果我们间接创建了一个引用的引用,则这些引用形成了引用折叠。正常情况下,不能直接创建引用的引用,但是可以间接创建。大部分情况下,引用的引用会折叠为普通的左值引用(T& &、T& &&、 T&& &),右值引用的右值引用,分折叠成右值引用。
f): 通过两个规则,得出一个结论:如果一个函数模板参数为右值引用,则可以传递给它任意类型的实参 -
引用折叠,引出的std::move的实现:
template <typename T> typename remove_reference<T>::type&& move(T&& t) { return static_cast<typename remove_reference<T>::type&&>(t); }
控制模板的实例化
-
编译器对模板的处理:当编译器遇到模板定义时,并不生成代码,而是当实例化模板时,才会生成代码。也就是说,使用的时候才生成代码。
-
通常情况下,当模板被使用时,才会进行实例化。 这意味着,相同的实例可能出现在多个对象文件中。在大的系统中,多个文件实例化相同模板的开销会很大,后处理时还要相办法只保留一份。
-
在新的标准中,可以通过显示实例化来控制实例化的过程。
-
实例化的声明,格式为:
extern template declaration
, 其中declaration 是类或函数声明,并且其中模板参数都已经被替换为模板的实参。在实例化的声明与定义之前,都应该已经定义好了模板类或者模板函数,否则编译报错。// 实例化的声明 extern template class Blob<int>; extern template Func(int a); // 模板类或模板函数的声明,注意区分它们。 template< typname T> class A; template<typename T> void Func();
-
实例化定义, 格式为:
template declaration
// 实例化的定义 template class Blob<int>; template Func(int a);
-
额外知识点:
a). 当编译器遇到extern时,不会在本文件中生成实例化的代码。
b). 将一个实例化的声明为extern就表示了在程序的其它位置有该实例化(不包含extern)
c). 一个类模板的实例化定义会实例化所有的成员函数。 这与处理类模板的普通实例化是不相同的(通常情况下,使用到的时候才进行实例化)
模板的特例化
-
特例化, 就是我们人工地为编译器针对模板做了推导工作,遇到相同参数类型时,请使用人工给的模板样例,编译器别自己推断了。
-
特例化与实例化是不同的。 实例化,编译器会生成实例代码的,比如一个函数模板,实例化时,会生成对应的函数。特例化只是给了一个人工的模板,编译器实例化时,请优先使用我给的特例化模板。
-
全特例化:把所有的形参都进行特例化.
template <typename T1, typename T2> void Func() { cout << "hello" << endl; } template<> void Func<int ,double>() { cout << "wow" << endl; }
-
部分特例化:
a). 类模板可以部分特例化,函数模板不可以部分特例化。
b). 部分特例化分两种:特例化一部分参数和特例化参数的一部分。// 特例化一部分参数 template <typename T1, typename T2> class A { public: A() { cout << "general" << endl; } }; template<typename T> class A<int, T> { public: A() { cout << "first int" << endl; } }; template<typename T> class A<T, double> { public: A() { cout << "first double" << endl; } }; A<char, char> a; A<int ,char> b; A<char, double> c; // 特列化参数的一部分 template<typename T> class A { public: A() { cout << "general " << endl;} }; template<typename T> class A<T&> { public: A() { cout << "reference " << endl;} }; template<typename T> class A<T*> { public: A() { cout << "pointer " << endl;} }; A<int> a; A<int&> b; A<int*> c;