读书笔记:C++ primer 5th edition--chapter16.模板与泛型编程

来源:互联网 发布:数据库sql种类 编辑:程序博客网 时间:2024/06/08 13:53

part1.定义模板

1.模板是泛型编程的基础
2.一个模板就是一个编译器用来生成特定类类型或函数的蓝图。
3.标准库算法都是函数模板,标准库容器都是类模板。
4.除了定义类型参数,也可以在模板中定义非类型参数,表示一个值,而非类型。
1)我们通过特定的类型名而非关键字class或者typename来指定非类型参数。
template < unsigned N, unsigned M> ...
2)一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或左值引用。
绑定到非类型整型参数的实参必须是一个常量表达式。
绑定到指针或引用非类型参数的实参必须具有静态的生存期。
5.inline或constexpr也是ok的
template < typename T > inline T min (const T&, const T&);
6.模板程序应该尽量减少对实参类型的要求
7.只有当我们实例化(也就是使用,而非定义)出模板的一个特定版本时,编译器才会生成代码。为了生成一个实例化的版本,编译器需要掌握函数模板或类模板成员函数的定义。因此其定义通常放在头文件中。而且我们只有到了实例化模板的阶段才会获得编译错误。
8.类模板
1)无法推断模板参数类型,必须额外提供信息用来代替模板参数的模板实参列表。这些额外信息呢是显示模板实参列表,被绑定到模板参数。
2)类模板的每个示例都是一个独立的类
3)成员函数只有在被使用的时候才会实例化。
4)类的作用域内,编译器处理模板自身引用的时候就好像我们已经提供了与模板参数匹配的实参一样。
BlobPtr& operator++()
BlobPtr < T > & operator++();
也就是可以直接使用模板名而不必指定模板实参。
5)如果类包含模板友元,类可以授权给特定友元实例,或者全部友元实例。
6)c++11允许把模板类型参数声明为友元:
template <typename Type > class Bar {
friend Type;
...
};
7)也可以定义模板类型别名
template < typename T> using twin = pair< T, T >;
twin< string > authors;
也可以固定一个或多个模板参数:
template < typename T> using partNo = pair< T, unsigned >;
也可以声明static成员。
9.模板参数
1)一个特定文件所需要的所有模板声明通常一起放在文件开始位置,出现于任何使用这些模板的代码之前。
2)使用类的类型成员。默认情况下,c++假定通过作用域运算符访问的名字不是类型。
如果需要使用类型,通过typename来指定。不能用class。
3)默认模板实参,与函数实参一样是右侧对齐的。
4)<>指出类必须从一个模板实例化而来。如果类模板为其所有的模板参数都提供了默认实参,且希望使用默认实参在模板名后面跟一个空的<>。
10.成员模板
1)一个类包含本身是模板的成员函数,这种成员叫做成员模板。不能为虚函数。
2)普通类的成员模板
以删除器为例,希望它可以用正在任何类型,所以将调用运算符定义为一个模板:
class DebugDelete {
public :
     template < typename T > void operator () (T *p) const {
          os << “” deleting “ << endl;
           delete p;    
     }
}
3)类模板的成员模板
此时类和成员各自有各自的独立的模板参数。
当我们在类模板外定义一个成员模板时,必须同时为类模板和成员模板提供参数列表。
template < typename T >
tempate < typename It >
     Blob < T > :: Blob ( It beg, It end):
     data(std: make_shared< std::vector< T > (beg, end) ) {};
//实例化
int ia[] = {0,1,2,3,4};
Blob<int > a1(begin(ia) , end(ia) );
10.控制实例化
1)为了避免在多个文件中实例化相同模板的额外开销,通过显示实例化。
//这些模板必须在程序其他位置进行实例化
extern template class Blob< string>;
2)在类模板的实例化定义中,所有类型必须能用于模板的所有成员函数。因为实例化定义会实例化所有成员。
11.效率与灵活性
1)shared_ptr给予共享指针所有权的能力,unique_ptr则独占指针
2)运行时绑定删除器
在一个shared_ptr生存期中我们可以随时改变删除其的类型。而类成员在运行时是不能改变的,因此不能直接保存删除器。
其析构函数会包含:
del? del (p):delete p;//dep(p)需要运行时跳转到del的地址,使得重载删除其更方便
3)unique_ptr 编译时绑定删除器
del(p);//避免了间接调用删除器的运行时开销。

part2.模板实参推断

1.模板实参推断
1)类型转换与模板类型参数
A.将实参传递给带模板类型的函数形参时,能够自动应用的类型转换只有const转换及数组或函数到指针的转换。
B.如果形参是引用,则数组不会转换为指针。
C.如果希望对函数进行正常的类型转换,可以将函数模板定义为不同类型:
template < typename A, typename B >
int flexibleCompare(const A& v1, const B& v2) ...
D.如果函数参数类型不是模板参数,则实参可以进行正常的类型转换。
2)函数模板显示实参。。这里没搞懂区别
可以提供显示模板实参
template < typename T1, typename T2, typename T3>
T3 alternative_sum( T2, T1);
3)尾置返回类型与类型转换
A.尾置返回类型
template < typename It > 
auto fcn(It beg, It end ) -> decltype( * beg ) {
     xx
     return *beg;
}
B.类型转换
remove_reference < decltype( * beg ) > :: type;//得到元素类型本身
如果不必要转换模板参数,则type成员就是模板参数类型本身。
4)函数指针和实参推断
当我们用一个函数模板初始化一个函数指针或为一个函数指针赋值时,编译器使用指针类型来推断模板实参。
template < typename T > int compare(const T&,const T&);
//pf1指向示例int compare xxx
int (*pf1) ( const int&, const int&) = compare;
void func( int (*) (const string &, const string &) );
func(compare);//func的重载版本必须唯一。否则需要制定。比如:
func(compare<int>);
5)模板实参推断引用。。没看懂,先跳过
如果一个函数参数是指向模板参数类型的右值引用,(T&&),则可以传递给它任意类型的实参。如果将一个左值传递给这样的参数,则函数参数被实例化为一个普通的左值引用(T&)。
2.move。。跳过
1)用法1
string s1(“hi”), s2;
s2 = std:move(string(“byte!”));     //从一个右值引用移动数据
s2 = std:move(s1);//正确,s2的值不确定??
2)提升swap性能:
普通swap:
    template <class T> swap(T& a, T& b) 
    { 
        T tmp(a);   // copy a to tmp 
        a = b;      // copy b to a 
        b = tmp;    // copy tmp to b 
 }
改进之后的:
   template <class T> swap(T& a, T& b) 
    { 
        T tmp(std::move(a)); // move a to tmp 
        a = std::move(b);    // move b to a 
        b = std::move(tmp);  // move tmp to b 
 }
3)另一个好处是:STL可能要求容器类型为可复制类型,但是大多数情况下只需要可移动性就够了,因此可以使用不可以复制但是可移动的类型,比如unique_ptr,作为容器元素。

3.转发
0)完美转发的定义:需要将一组参数原封不动地传递给另一个函数。除了参数值不动之外,还有两组属性:左值右值,const和非const。
因此对于一个参数就要重载两次。(T&, const T&),重载次数与参数个数成正比。
 template <typename T> void forward_value(const T& val) { 
  process_value(val); 
 } 
 template <typename T> void forward_value(T& val) { 
  process_value(val); 
 }
使得可以满足下面的调用:
  int a = 0; 
  const int &b = 1; 
  forward_value(a); // int& 
  forward_value(b); // const int& 
 forward_value(2); // int&
如果用右值引用的话,很简洁:
 template <typename T> void forward_value(T&& val) { 
  process_value(val); 
 }

可以满足上述的调用

1)如果一个函数参数是指向模板类型参数的右值引用,它对应的实参的const属性和左值/右值属性将得到保持。
template < typename F, typename T1, typename T2>
void flip2( F f, T1 && t1, T2 &&t2 )
{
     f(t2, t1);//or: f(std::forward< T2 > (t2), std::forwad< T1 > (t1) );
}
2)当用于一个指向模板参数类型的右值引用函数参数(T&&)时,forward会保持实参类型的所有细节。

part3.重载与模板

1.匹配原则类似:
1)候选函数包括所有模板实参推断
2)候选的函数模板总是可行的
3)可行函数按照类型转化来排序
4)如果多个函数提供同样好的匹配,则:选择唯一的非模板函数,或更特例化的版本,否则有歧义。
2.对于重载函数模板的函数而言,如果编译器可以从模板实例化出与调用版本匹配的版本,那么缺少该函数的声明也ok。

part4.可变参数模板

1.两种参数包:模板参数包,函数参数包,可以为0个或多个
2.可变参数函数通常是递归的。
//第一个用来终止递归
template < typename T >
ostream &print (ostream &os, const T &t ){
     return os << t;
}
//
template < typename T ,typename… Args>
ostream &print( ostream &os, const T &t, const Args&… rest)
{
     os << t << “, “;
     return print(os, rest…);//递归调用,打印其他实参
}
3.执行顺序
print( cout, i ,s ,42);
// print( cout, i ,s ,42);->print( cout,s ,42);->print( cout, 42);
4.包扩展,扩展模式会独立应用于包中的每个元素
template < typename… Args>
ostream &errorMsg( ostream &os,  const Args&… rest){
     return print (os, debug_rep(rest)…);//扩展为:print(cerr, deug_rep(fcnName), debug_rep(code.num());
}
//while
template < typename… Args>
ostream &errorMsg( ostream &os,  const Args&… rest){
     return print (os, debug_rep(rest...));//扩展为:print(cerr, deug_rep(fcnName, code.num());不正确
}
5.转发参数包
可变参数函数通常将它们的参数转发给其他函数。形式一般如下:
template < typename… Args >
void fun( Args&… args){
     //同时扩展Args,args
     word ( std::forward< Args>(args)…);
}

part5.模板特例化

1.一个特例化版本就是模板的一个独立的定义,在其中一个或多个模板参数被指定为特定的类型。
2.函数模板特例化
1)本质是实例化一个模板,而非重载,不会影响函数匹配。
2)流程
首先有:
template< typename T> int compare( const T&, const T&);//常量指针
然后:
template <>
int compare(const char* const &p1, const char* const &p2){     //要求指向const char的const指针的引用
     return strcmp(p1, p2);
}
3)模板及其特例化应该声明在同一个头文件中。由于丢失特例化版本时编译器通常会实例化原模板,容易产生模板及其特例化版本声明顺序导致的错误,很难查找。
3.类模板特例化
为了使自己的数据类型也能使用hash来组织,必须定义hash模板的一个特例化版本。
1)必须在原模板定义所在的命名空间中特例化。为此要打开命名空间:namespace std{/***/};//***将被加入std
2)demo
size_t
hash < Sales_data> :: operator() (const Sales_data& s) const
{
     return hash<string>() (s.bookNo_ ^
               hash<unsigned>() (s.units_sold) ^
               hash<double>() (s.revenue);
}
3)由于hash<Sales_data>使用Sales_data的私有成员,要声明为友元:
template< class T> class std:hash;
class Sales_data{
     friend class std::hash < Sales_data>;
}
4.类模板部分特例化
只能部分特例化类模板,不能部分特例化函数模板。
我们可以值特例化特定成员函数,而不是整个模板。

part6.用途
1.自己写库或者用别人的库的时候会用到
2.泛型泛型,型不泛的时候泛什么型。

0 0
原创粉丝点击