C/C++基础--模板与泛型编程

时间:2021-05-30 15:21:30
  • 模板参数

函数模板,编译器根据实参来为我们推断模板实参。

模板中可以定义非类型参数,表示一个值而非一个类型,这些值必须是常量表达式,从而允许编译器在编译时实例化模板。

非类型参数可以是整型,或者一个指向对象或函数的指针或(左值)引用。绑定到前者的实参必须是常量表达式,绑定到后者的必须具有静态生存期。

  • 泛型代码两个原则

1模板中的函数参数是const的引用

2函数体中的条件判断仅适用<比较运算

第一条保证了函数可以用于不能拷贝的类型

第二条降低了对要处理类型的要求,模板应该尽量减少对实参类型的要求

  • 实例化
模板的头文件通常既包含声明也包括定义
模板提供者保证模板被实例化时,模板的定义(包括成员的定义)都必须是可见的。
用户需保证用来实例化模板的所有函数、类型以及类型关联的运算符声明都必须是可见的。 成员函数只有在被用到时才进行实例化。使得某些不完全复合模板操作要求的类型也能实例化类,但是不能用那些特性的操作。
在类模板自己的作用域中可以直接使用模板名而不提供实参,在外部必须提供
template <typename T>
BlobPtr<T> BlobPtr<T>::operator++(int)
{ BlobPtr ret=*this;//里面不需要
} 需要先前置声明为模板,将模板的一个特定实例声明为友元时
一对一友好关系
friend class BlobPtr<T>;
friend bool operator==<T> (const Blob<T>&, const Blob<T>&); 通用友好关系
template <typename X> friend class Pal2;//不需要前置声明,使用不同的模板参数X
  • 类型别名
typedef只能医用实例化的类
dypedef Blob<string> StrBlob;
新标准允许为类模板定义一个类型别名
template <typename T> using twin=pair<T, T>;
twin<string> authors;//authors是一个pair<string, string>
类型别名可以固定一个或多个模板参数
template <typename T> using partNo=pair<T, unsigned>; 一个特定文件用到所有模板声明通常一起放在文件的开始位置。不必担心编译器由于未遇到你希望调用的函数而实例化一个并非你需要的版本。 C++默认通过作用域访问的名字不是类型,所以如果我们希望使用模板类型参数的类型成员,必须显式地告诉编译器该名字是一个类型。
template <typename T>
typename T::value_type top(const T& c) //返回类型是一个类型
  • 可以为函数和类模板提供默认实参。

无论何时使用类模板必须在模板名后加上尖括号,如果所有模板参数都提供了默认实参,而我们又希望使用默认实参,则加一个空的尖括号。

  • 成员模板不能是虚函数
在类外定义时,同时为类模板和成员模板提供模板参数列表
template <typename T>
template <typename It>
Blob<T>::Blob(It b, It e); 多个文件中实例化相同模板有额外开销,在大系统中可能非常严重
可以显式实例化,声明必须在任何使用此实例版本的代码之前
extern template class Blob<string>; // 实例化声明
template int compare(cons tint&, cons tint&);//实例化定义
显式实例化的类模板,必须能用于模板的所有成员
  • 效率和灵活性
运行时绑定删除器,删除器是间接保存的,调用del(p)时需要一次运行时的跳转操作
del ? del(p) : delete p;// del(p)需要运行时跳转到del的地址 编译时绑定
del(p); //直接调用实例化的删除器,无运行时额外开销
  • 能应用于函数模板的类型转换
1const转换:非const对象的引用(或指针)传递给const的引用(指针)形参
2数组或函数:如函数形参不是引用,可以对数组或函数类型的实参应用正常的指针转换
如函数参数类型不是模板参数,则可以进行正常类型转换。
  • 函数模板显式实参
1编译器无法推断(如返回类型) 2希望允许用户控制模板实例化
template < typename T1, typename T2, typename T3>
T1 sum(T2, T3); auto val3= sum<long long>(i, lng); //long long sum(int, lonog)
显式模板实参由左至右的顺序与对应的模板参数匹配。
由于尾至返回出现在参数列表之后,它可以使用函数的参数
template < typename It>
auto fcn(It beg, It end) -> decltype(*beg)

有时我们无法直接获得所需要的类型

  • 标准类型转换模板

    对Mod,其中Mod为 |若T为 |则Mod::type为|

    |remove_reference| X&或X&&| X|

    ||否则| T|

    |add_count| X&、const X或函数|T|

    ||否则 |const T|

    |add_lvalue_reference| X&|T|

    ||X&&|X&|

    ||否则 |T&|

    |add_rvalue_reference |X&或X&&|T|

    ||否则| T&&|

    |remove_pointer |X|X|

    ||否则| T|

    |add_pointer |X&或X&&|X
    |

    ||否则| T*|

    |make_signed| unsigned X|X|

    ||否则 |T|

    |make_unsigned| signed X|unsigned X|

    ||否则|T|

    |remove_extent| X[n]|X|

    ||否则| T|

    |remove_all_extents| X[n1][n2]…|X|

    ||否则| T|

template void f1(T&); // 实参必须是一个左值

template void f2(const T&); // 实参可以接受一个右值

template void f3(T&&); // 可以右值,也可以左值

  • 右值引用,move,forward
通常一个右值引用不能绑定到一个左值。
两个例外
1当将一个左值传递给函数的右值引用,且此右值引用指向模板类型参数(如T&&)时,编译器推断模板类型参数为实参的左值引用类型。调用f3(i),T推断为int&
通常不能定义一个引用的引用,但是通过类型别名或通过模板类型参数间接定义是可以的
2如果我们间隔创建一个引用的引用,则这些引用形成了折叠
X& &、X& &&和X&& &都折叠为类型X&
X&& &&折叠为X&& 所以一个函数参数是指向模板参数类型的右值引用,则可以传递给他任意类型的实参。它对应实参的const属性和左右值属性得到保持。
通常用于模板转发其实参或模板被重载。
重载通常按以下方式来
template <typename T> void f(T&&); //绑定到非const右值
template <typename T> void f(const T&) //绑定到左值和const右值 std::move的函数参数T&&是一个指向模板类型参数的右值引用,经过引用折叠可以与任何类型的实参匹配,返回都是一个右值引用
static_cast指针右值引用的特例:显式地将左值转换为一个右值引用 当用于一个指向模板参数类型的右值引用函数参数时,forward会保持实参类型的所有细节。
template <typename T> void f(T &&arg)
{
g(std::forward<T>(arg));
}
首先选择更好的匹配,对于同样好的匹配
优先选择非模板函数
其次是更特例化的模板
如果找不出更好的,则此调用有歧义。
  • 可变参数模板
template <typename T, typename…Args>
void foo(const T &t, const Args& … rest);
sizeof…(Args)知道参数包中元素的个数,不会对实参求值 可变参数函数通常是递归的。为了终止递归需要定义一个非可变参数版本,否则会无限递归。 包扩展就是将它分解为构成的元素,对每个元素应用模式。通过在模式右边放一个省略号来触发扩展。
template < typename…Args>
ostream& errMsg(ostream &os, const Args& … rest)
{
return print(os, debug_rep(rest)…);
} 转发参数包
alloc.construct(first_free++, std::forward<Args>…);
  • 模板特例化
1通用模板的定义对特定类型不适合
2可以利用特定知识来编写更搞笑的代码
template <> //尖括号指出我们将为原模板所有模板参数提供实参
一个特例化版本本质上是一个实例,而不是函数名的一个重载版本
一个特殊的函数定义是特例化版本还是一个独立的非模板函数,会影响到函数匹配。 为了特例化一个模板,原模板的声明必须在作用域中。而且,在任何使用模板实例的代码之前,特例化版本的声明也必须在作用域中。
通常模板及特例化版本应该声明在同一个头文件中。所有同名木板的声明应该放在前面,然后是这些模板的特例化版本。 为了让Sales_data的用户能使用hash的特例化版本,我们应该在Sales_data的头文件中定义该特例化版本 偏特化只能作用于类模板,而不能用于函数模板
偏特化模板参数列表是原模板的一个子集或者是一个特例化版本
template <typename T> struct remove_reference template <typename T> struct remove_reference<T&>
template <typename T> struct remove_reference<T&&> 可以只特例化成员而不是类
template<>
void Foo<int>::Bar()