类型参数
typename声明的参数都属于类型参数,它的实参必须为系统内置或者用户自定义的数据类型,包括类模板实体,由类模板产生的类模板实体,本质上就是类。ios
非类型参数
C++容许人们在模板参数列表中像函数参数列表中那样定义普通变量或者对象。定义的普通变量不能被修改,由于模板参数是在预编译期间进行传递而且被编译的。仅支持能够转换为Int类型的变量(double都不行!)、枚举、指针、引用。c++
template<typename T, int b>
模板定义型参数
C++也容许以类模板的定义做为类模板参数,之因此须要这种参数,其目的就是除了强调这个参数的实参必须为类模板外,还强调这个类模板所具备的参数个数。算法
//其中T就是一个单参数类模板 template<template<typename S>class T>
左值值既能够出如今赋值运算符的左边和右边,右值只能够出如今赋值运算法的右边。左值之因此能够出如今赋值运算符的左边,就是由于这种表达式表明一块存储空间,能够接受并保存数据。程序能够经过其变量名获取地址,并用这个地址访问数据。右值仅能表明数据。右值表达式要么是数据自己,要么是一个能得出结果的运算表达式,尽管它也占据必定的存储空间,可是由于它没有名字,也不能从其表达式中提取这个空间的地址。所以这种表达式只能出如今赋值运算符的右边,并且仅能表明生命期与其所在语句相同的临时对象。
关于引用,在C++11以前,左值能够定义两种引用:左值常引用和左值引用。对于右值,C++11以前仅定义了一种常量引用。对右值进行常量引用能够延长右值的生命期,从而使后续程序能够利用它的信息,遗憾的就是它是常量,不能知足程序更多的要求。
为了能充分利用临时对象,C++11标准推出了一种新的数据类型——右值的很是量引用,简称右值引用。ide
T&& name = rvalue;
深拷贝与浅拷贝:函数
#include <iostream> using namespace std; class Student { private: int num; char *name; public: Student(); ~Student(); }; Student::Student(){ name = new char(20); cout << "Student" << endl; } Student::~Student(){ cout << "~Student " << (int)name << endl; delete name; name = NULL; } int main() { Student s1; // 使用了默认拷贝函数,默认拷贝函数进行的是浅拷贝 // 所以在析构的时候会对同一个内存空间释放两次,形成内存泄漏。 Student s2(s1); return 0; }
浅拷贝优势是速度快,节省资源,缺点是共享了资源,容易引发内存泄漏。深拷贝不会引发泄漏,可是每次拷贝都消耗大量资源。优化
转移语义:spa
//关闭RVO,return value optimistic,返回值优化 //不关闭在返回右值的时候会跳过移动构造函数,直接构造对象。 g++ -fno-elide-constructors e1_b.cpp -o e1_b && ./e1_b Foo fuct(){ Foo foof(100); //产生局部变量,生命周期只有在fuct()函数中,在函数返回的时候会被析构。 return foof; //这里实际上是调用了Foo类的拷贝函数,拷贝了一份做为返回值。 } fuct(); Foo(Foo&& r) //调用拷贝函数,返回一份拷贝 ~Foo() //函数中的局部变量生命周期结束 ~Foo() //函数返回值的生命周期结束,在主函数return的时候 //为何会调用了一次析构函数呢? //由于foo1的生命周期是整个主函数,少的那一次在主函数return的时候会被调用 //不是初始化!没有调用构造函数!声明了一个常引用接受fuct()的返回值。 const Foo& foo1 = fuct(); Foo(Foo&& r) //拷贝局部变量做为返回值 ~Foo() //局部变量声明周期结束 //与上相同,只是一个是常引用,另外一个是很是量引用 Foo&& foo2 = fuct(); Foo(Foo&& r) ~Foo() //隐式调用构造函数和显式调用构造函数! Foo foo3(fuct()); Foo foo3 = Foo(fuct()); Foo(Foo&& r) //拷贝做为返回 ~Foo() //析构局部变量 Foo(Foo&& r) //调用拷贝构造函数,建立对象foo3, ~Foo() //返回值声明周期结束,调用析构函数 return 0!
在某些状况下,被拷贝的对象是右值,意味着其生命周期即将结束,此时若是咱们再去消耗资源开辟新空间就显得浪费了。由于被拷贝对象即将”死亡“,咱们不妨借用一下这个右值的内存空间!这就是语义转移,即便用浅拷贝共享内存空间后,将”强迫“原对象放弃资源控制权(指针为空),避免内存空间被释放。经过这种方式,原对象(右值)的内存空间,被咱们转移(窃取)出来,用于新对象,经过这种方式,咱们极大地节省了开销。设计
看到了右值引用的好处,左值引用也想利益共沾。可是右值引用,必须参数要是右值才能完成语义转移,由于左值在逻辑上是不容许被窃取内存空间的。指针
T&& std::move(T&); void swap(T& a, T& b){ T tmp = std::move(a); a = std::move(b); b = std::move(tmp); }
在程序设计实践中,函数模板常常须要调用一些外部函数实现它的功能,这时候就须要模板为这些函数传递参数,人们习惯上把模板的这种行为叫作参数转发。对于模板来讲,它的任务就是参数的忠实转发,一不能改变参数特性,二不能产生额外开销。若是模板能把全部数据类型都按照上述要求进行转发,那么这个模板就是一个完美参数转发者。code
//方案一: void Func(int); //目标函数 template<typename T> Tmp(T); //转发模板 //结果:这种方式在Tmp(10),参数为右值的时候,在经模板转发后变成了左值 //方案二: void Func(int v); template<typename T> Tmp(T&); //结果:没法转发右值,Tmp(100)报错,由于模板为左值引用 //改进,Tmp(const T&),此时模板转发后参数变为常引用,可能不符合某些函数要求 //方案三: void Func(int& v); template<typename T> Tmp(T&& a){ Func(a); } //结果:C++11前没法经过,由于C++11前尚未右值引用T&&,以及对多个引用符&连用的数据类型进行推导的功能 /* 多重引用推导表 实参类型 模板参数 推导类型 int& T& int& int&& T& int& int& T&& int& int&& T&& int&& lvalue T&& T& 左值 + T&& = T& rvalue T&& T 右值 + T&& = T(左值) */ //在这种推导状况下,若是Tmp的参数为右值,在转发后参数类型就会被推导为左值,这与咱们的预期不一样。 //对此咱们在Tmp函数内对参数进行强制的类型转换 Tmp(T&& a){ Func(static_cast<T&&>(a)); } //由于左值+T&& = T&&, T& + T&& = T&, 实现了参数的完美转发。 //为了区别于move()和static_cast,并使之更具备语义性,C++11将static_cast()封装在函数模板std:forward()。 Tmp(T&& a){ func(std::forward<T>(a)); }