C++ template

模板面试知识点

  1. 模板特化和偏特化[2]。
  2. 函数模板和类模板的定义通常需要放在头文件中。为了生成一个实例化版本,编译器需要掌握函数模板或者类模板成员函数的定义[3]。
  3. 函数重载和模板特化的关系。模板特化不影响函数匹配,实际上,我们是替编译器实现了重载之后的某个特殊类型的实例化。

一些函数

  1. remove_reference,可以将一个引用类型(包括左值引用和右值引用)变成非引用类型,通过类模板特化完成。
  2. std::move,将一个左值转换成右值,或者保持右值不变。通过static_cast完成。
  3. std::forward<T>(arg)
    1
    2
    3
    4
    5
    template<typename Type>
    void func(Type && arg)
    {
    another_func(std::forward<Type>(arg));
    }

上述代码会做到将func的实参原封不动的(保持所有属性)转发到another_func中,具体的过程:首先根据引用折叠和右值引用参数推断出Type的类型,如果arg是右值,Type是普通类型,如果arg是左值,则Type是引用的右值,通过引用折叠,得到Type是一个左值引用。推断出Type的类型之后,std::forward返回的是Type的&&,再和arg进行引用折叠,得到相应的左值或者右值。

模板定义

模板参数列表

模板定义以关键字template开始,后跟一个模板参数列表,是一个逗号分隔的一个或者多个模板参数的列表,用<>分隔开来。模板参数可以是模板类型参数和非类型模板参数。

模板类型参数

一个模板类型参数表示一个类型,类型参数前需要加上class或者typename关键字,它们是等价的。

非类型模板参数

而非类型模板参数是一个值,而不是一个类型,通过一个特定的类型名指定非类型参数。

模板参数列表的作用很像函数参数列表。函数参数列表定义了若干特定类型的局部变量,但是没有指出如何初始化它们。运行时,需要调用者提供实参来初始化形参。而模板参数表示在类或者函数定义中用到的类型或者值。使用模板时,我们需要显式或者隐式的指定模板实参,将它绑定到模板参数上。

模板种类

函数模板

调用函数模板时,编译器会用函数实参推断模板实参。

类模板

编译器不能为类模板推断模板类型参数,为了使用类模板,必须在模板名后的尖括号提供模板实参列表代替模板参数。

  1. 实例化类模板。
    使用类模板时,需要提供显式模板实参列表,编译器使用这些模板实参进行实例化。
  2. 类模板的成员函数。
    定义在类模板之外的成员函数必须以关键字template开始,后接类模板参数列表。在类外定义一个成员时,不仅要说明成员属于哪个类,从一个模板生成的类的名字中必须包含它的模板实参(比如Blob<T>)。
  3. 类模板成员函数的实例化。
    对于一个实例化了的类模板,它的成员只有在使用时才被实例化,所以即使某种类型不完全符合模板操作的类型,也能用这个类型实例化类。
  4. 在类的作用域内简化模板类名的使用。
    在类模板自己的作用域内,不需要使用Blob<T>,使用Blob即可。
  5. 类模板和友元。
    仅仅每个实例类型之间互为友元。(template_friend_1.cpp)
    一个类可以将另一个模板的所有实例都声明为友元。(template_friend_2.cpp)
    一个类可以是一个类模板所有实例的友元。(template_friend_3.cpp)
    一个类模板将另一个类模板的所有实例都声明为友元。(template_friend_4.cpp)
    一个模板将模板类型参数声明为友元。
  6. 模板类型别名。
    使用using可以为类模板定义一个别名。
  7. 类模板的static成员。
    每个实例都有一个自己的static成员。

模板参数

  1. 模板参数和作用域。
    模板内的变量名不能和模板参数名冲突。
  2. 使用类的类型成员。
    默认情况下,假定通过作用域运算符访问的是名字不是类型。如果需要访问类型的话,必须使用typename关键字告诉编译器这是一个类型。
  3. 默认模板实参。
    C++ 11之后,可以为函数和类模板都提供默认实参。在使用类模板的时候,必须在模板名后加上尖括号,如果一个类模板的所有模板参数都有默认实参,而且想要使用默认实参,需要使用`<>。

成员模板

一个类(普通类或者类模板)都可以包含本身是模板的函数,这种成员被称为成员模板,成员模板不能是虚函数。

  1. 普通类的成员模板。
  2. 类模板的成员模板。
    类模板和成员模板有各自的模板参数。当在类模板外定义成员模板的时候,必须同时为类模板和成员模板提供模板参数列表,类模板的参数列表在前,成员模板的参数列表在后。

实例化定义

模板在使用时才被实例化,当多个独立编译的源文件同时使用了相同的模板和模板参数列表时,可能而存在多个同一模板的示例,使用显式实例化避免这种开销。

1
2
3
4
5
extern template declaration;    //实例化声明
template declaration; //实例化定义
// 示例
extern template Blob<string>;
template Blob<string>;

实例化定义会实例化所有的成员,所以用来实例化的类型,必须能用于模板的所有成员。

效率和灵活性

shared_ptr在运行时绑定deleter,deleter不是shared_ptr的成员。
unique_ptr在编译是绑定deleter,deleter是unique_ptr的一个成员。

模板实参推断

类型转换和模板类型参数

  1. 能用于函数模板的类型转化。
    • const转换,可以将非顶层const 转换为顶层const
    • 数组或者函数转换为相应的指针。
  2. 相同模板类型参数的函数形参。
  3. 不同模板类型参数的函数形参。
  4. 正常类型转换对应于普通函数实参。

函数模板显式实参

对于一些编译器无法推断出的模板实参类型,可以指定显式模板实参。而且显式模板实参的顺序和模板参数声明的顺序一致。

尾置返回类型

可以使用尾置返回类型声明不知道返回结果的类型。

函数指针的实参推断

当参数是一个函数模板实例的地址时,程序的上下文必须满足,对于每个模板参数,能唯一确定它的类型或者值。

模板实参推断和引用

  1. 当函数参数是一个普通左值引用的时候,只能给他传递一个左值,实参可以是const,也可以不是。如果实参是const的,T会被推断成const类型。
    当函数参数是一个常量引用的时候,实参可以是任何对象。这个const是函数参数类型的一部分,而不是模板参数类型的一部分。
  2. 也可以推断出右值实参的类型。
  3. 引用折叠和右值引用参数。如果一个函数参数是指向模板参数类型的右值引用,比如T&& val,可以给val传递任意类型的实参(左值或者右值)。如果将一个左值传递给这样的参数,则函数参数被实例化为一个普通的左值引用

理解std::move

std::move的源代码如下:

1
2
3
4
5
template<typename T>
typename remove_reference<T>::type&& move(T&& t)
{
return static_cast<typename remove_reference<T>::type&&>(t);
}

所以,从代码中我们可以看出来,是可以显式的将一个左值static_cast到一个右值引用。

forward

forward指的是将实参的所有性质原封不动的转发给其他函数,包括实参类型是否是const的以及实参是左值还是右值。
**forward的返回类型是T&&。**如下代码:

1
2
3
4
5
6
7
8
9
template<Typename T>
void func(Type && arg)
{

// 首先对右值引用模板参数进行推导,如果arg是右值,那么推断出Type是普通非引用类型。如果arg是左值,推断出Type是左值引用类型。
std::forward<Type>(arg);
// 调用std::forward获得Type的右值引用,通过引用折叠,可以得到相应的左值和右值类型变量。

}

重载与模板

函数模板匹配规则,如果有多个函数都满足要求,选择其中最特殊的那个:普通函数比函数模板特殊,都是函数模板的时候,选择更特殊的那个,否则就有歧义。

可变参数模板

可变参数模板是一个接收可变数目参数的模板函数或模板类。可变数目的参数被称为参数包。存在两种参数包:模板参数包和函数参数包,模板参数包表示零个或者多个模板参数,就是typename加上… ,函数参数包表示零个或者多个函数参数,是模板类型加上…。

模板特化

函数模板特化和类模板特化。特化一个函数模板时,必须为原函数模板中的每个模板参数都提供实参,叫做全特化。类模板的特化不需要给所有的模板参数提供实参,可以指定一部分参数而不是所有参数,即偏特化。
特化的本质是实例化,和模板的重载本质上是不同的,特化不会影响函数匹配。

参考文献

1.《C++ Primer》第五版中文版
2.https://stackoverflow.com/questions/8061456/c-function-template-partial-specialization
3.https://isocpp.org/wiki/faq/templates