模版与泛型编程

非类型模版参数

  非类型参数表示一个值而非一个类型,通过一个特定的类型名而非关键字typename或者class来指定非类型参数。当一个模版被实例化时,非类型参数被一个用户提供或者编译器推断出的值所代替。这些值必须是常量表达式,从而允许编译器在编译时实例化。(关键在于编译器的自动推断)

  例如编写一个compare版本用来处理字符字面常量。这种字面常量是const char的数组。由于不能拷贝数组,所以我们将自己的参数定义为数据的引用。由于我们希望比较不同长度的字符字面常量,因此为模版定义了两个非类型的参数。第一个模版参数表示第一个数组的长度,第二个参数表示第二个数组的长度。

template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M])
{
      return strcmp(p1, p2);    
}

  当我们调用compare("hi", "mom")的时候,实际上实例化出如下版本:

int compare(const char (&p1)[3], const char (&p2)[4])

 模版编译

  为了实例化一个模版,编译器需要掌握模版的声明以及定义,也就是模版的头文件通常既包括声明也包含定义。

   在一个类模版的作用域内, 可以直接使用模版名而不必指定模版实参。

类模版和友元素 

  

 

  上图中的前置声明要注意下咯,模版类的前置声明的形式(template <typename> class Blobptr)。

  友元的声明用Blob的模形参作为它们自己的模版实参。因此,友好关系被限定在用相同类型实例化的Blobptr与Blob相等运算符中: 

Blob<char> ca; // BlobPtr<char> 和 operator==<char>为本对象的友元

通用和特定的模板友好关系

 

   为了让所有实例成为友元,友元声明中必须使用于类模版本身不同的模版参数。

令模版自己的类型参数成为友元

template <typename Type> class Bar {
     friend Type;   //将访问权限授予用来实例化Bar的类型  
}

  对于某个类型名FOO而言,FOO将成为Bar<Foo>的友元。(静态成员可以通过对象或者作用域运算符调用,静态成员除了声明之外,必须有唯一的定义。) 

  当我们希望通知编译器一个名字表示类型的时候,必须使用关键字typename,而不能使用class。

 

 模版默认实参与类模版

 

类模板的成员模板

  

 

 

  在类模版外定义成员模版的时候,必须同时为类模版以及成员模版提供模版参数列表(类模版在前,成员模版在后)。 

控制实例化

  当两个或多个独立编译的源文件使用了相同的模版,并提供了相同的模版参数时,每个文件都会有该模版的一个实例。对于大文件系统而言,在多个文件中实例化相同模板带来的额外开销会非常严重,可以通过显示实例化来解决这一问题:

extern template declaration; // 实例化声明
template declaration; // 实例化定义

// e.g
extern template class Blob<string>;  // 声明
template int compare(const int&, const int&); // 定义

 当编译器遇到extern模版声明时,它不会在本文件中生成实例化的代码,而是从程序其他位置找到该实例化的一个非extern定义(声明)。例如:

 

 

 

   Application.cc中extern声明的模版就不会在本文件实例化,但是必须能够在其他文件中找到非extern的定义。

  一个类模版的实例化定义会实例化该模版的所有成员,包括内联的成员函数,显示实例化一个类模版的类型,必须能够用于模版的所有成员。

 模版实参推断

  从函数实参来确定模版实参的过程被称之为模版实参推断。如果一个函数形参的类型使用了模版类型参数,那么它将采用特殊的初始化规则。编译器通常不是对实参进行类型转换,而是生成一个新的模版实例。有以下规则:

  1. 顶层const会被忽略(常量指针,很好理解,由于是生成新的实例,自然指向的对象是可以改变的)。

  2. 可以将一个非const的引用对象(或指针)传递给一个const的饮用(或指针)形参。

  3. (形参非引用类型)一个数组实参可以转换为一个指向其首元素的指针;一个函数实参可以转换为一个该函数类型的指针。

函数显式实参

  如下函数定义:

template <typename T1, typename T2, typename T3>
T1 sum(T2, T3);

  sum函数没有任何函数实参可以用来推断T1,因此每次使用sum时必须为T1提供一个显示模版参数,如下:

auto val3 = sum<long long>(i, lng); // long long sum(int, long)

  注意,显示模版实参按由左至右的顺序与对应的模版参数匹配。模版类型参数已经显示指定了的函数实参,可以进行正常的类型转换。

尾置返回类型与类型转换

  如下函数:

 

   我们并不知道返回结果的准确类型,但知道是所需类型是所处理的序列的元素类型。可以考虑尾置返回类型结合decltype来确定类型。

 

   如果我们想利用fcn返回元素的拷贝,我们需要利用remove_reference来获得元素类型。我们用

remove_reference<decltype(*debug)>::type

  获得beg引用的元素类型,改进的函数如下:

 

   注意,这里的type是一个类的成员,该类依赖于一个模版参数,需要用typename显示的告诉编译器这是一个类型。

引用折叠和右值引用参数

  将一个左值传递给函数的右值引用参数,且此右值引用指向模版类型参数(如T&&),编译器推断模版类型参数为实参的左值引用类型。

  引用折叠只能应用于间接创建的引用的引用,如类型别名或模版参数。

  尽管static_cast只能用于其他合法的类型转换,但有个例外:可以用static_cast显式的将一个左值转换为一个右值引用。 

 转发(std::forward)(完美保持原有的类型)

  如果一个函数参数是指向模板类型参数的右值引用,它对应的实参的const属性和右值/左值属性将得到保持。

  当用于一个指向模版参数类型的右值引用函数参数(T&&)时,std::forward会保持实参的所有细节

函数重载与模版

 

可变参数模板 

  一个可变参数模版就是一个接受可变数目参数的模版函数/类。可变数目的参数称为参数包。可以通过sizeof运算符来计算包中有多少元素。

644)

posted @ 2021-07-15 09:32  猪突猛进!!!  阅读(37)  评论(0编辑  收藏  举报