完美转发
观察下面的代码
int main() { int x = 1; int y = 2; int&& rr1 = 0; const int&& rr2 = x + y; rr1++; rr2++; return 0; }
通过上面的学习肯定知道,右值被引用之后会被存储到特定的位置,可以对特定的位置进行取地址,所以rr1++正确;rr2++错误,因为其引用的数据被const所修饰不能进行修改
运行结果与预期一致
既然右值被引用之后可以进行”修改“,是不是可以理解为其属性变为了左值;再结合上面当进行移动构造时,右值本身是不能进行修改的,但是经过右值引用之后,其属性变为了左值,资源交换也就可以进行;所以,当右值被右值引用之后,其属性变为了左值
观察下列代码
void Fun1(int& x) { cout << "Fun1(int& x)" << endl; } void Fun1(int&& x) { cout << "Fun1(int&& x)" << endl; } int main() { int i = 0; Fun1(i); Fun1(0); return 0; }
左值匹配左值引用,右值匹配右值引用没有什么问题,当只有右值引用结果会是怎么样呢???
有结果可知,当左值去匹配右值引用时,程序崩溃;那么是不是存在某种函数,既可以匹配左值同时也可以匹配右值呢???
模板中的&&万能引用
template<class T> void PerfectForward(T&& t) { Fun1(t); }
万能模板可以匹配任何类型的数据,先检测上面的数据
int main() { int i = 0; PerfectForward(i); PerfectForward(0); return 0; }
程序正常运行,证明了万能模板可以匹配左值也可以匹配右值
图解:
当左值i匹配模板时,模板会将其推演为int类型,实际类型是int&;当右值0匹配模板时,模板将其推演为int,实际类型是int&&
万能模板虽然解决了类型匹配的问题,但是又引出了一个新问题,为什么程序运行的结果都是左值引用呢???
解释起来也很简单,当右值被右值引用之后其属性变成了左值,所以全都调用的左值引用
为了解决这个问题,又引入了完美转发std::forward,在右值引用之后会保持其属性
template<class T> void PerfectForward(T&& t) { Fun1(std::forward<T>(t)); }
至此,以上有关属性改变的问题已经全部解决
新的类功能
默认成员函数
C++11新增了两个默认成员函数:移动构造和移动赋值重载
对于新增的成员函数需要注意如下点:
如果没有实现移动构造函数,且没有实现析构函数,拷贝构造和赋值重载中的任一个,则编译器会自动生成一个默认移动构造:对于内置类型变量会执行按字节拷贝;对于自定义类型需要看成员是否实现移动构造,如果已经实现就调用移动构造,否则就调用拷贝构造
移动赋值重载亦是如此
强制生成默认成员函数的关键字:default
C++11可以让使用者更好地使用默认成员函数,如果需要使用某个默认函数,但是并没有实现,那么可以使用关键字default显示指定函数生成
namespace yjm { class Person { public: Person(const char* name = " ", int age = 0) :_name(name) , _age(age) { } Person(const Person& p) :_name(p._name) ,_age(p._age) { } Person(Person&& p) = default; private: string _name; int _age; }; } int main() { yjm::Person s1("yjm",20); yjm::Person s2(s1); //调用由关键字生成的右值引用构造函数 yjm::Person s3(std::move(s1)); return 0; }
禁止生成默认成员函数的关键字:delete
既然存在强制生成,那么就会存在禁止生成;关键字delete便是为了禁止生成某种默认成员函数
复用上面的代码,运行结果如下
被关键字 delete修饰的默认成员函数是不可以被调用的
可变参数模板
C++11的新特征可变参数模板,可以创建接受可变参数的函数和类模板
如下就是一个可变参数的函数模板
//Args是模板参数包,args是函数形参参数包 //声明参数包Args... args,这个参数包可以包含0到任意个模板参数 template<class ...Args> void showlist(Args... args) {}
上面的参数args前面有省略号,所以它就是一个可变模板参数,把带有省略号的参数称为参数包,其包含0到任意个模板参数;无法直接获取参数包args中的每个参数,只能通过展开参数包的方式来获取每个参数
递归函数方式展开参数包
template<class T> void showlist(const T& t) { cout << t << endl; } template<class T,class ...Args> void showlist(T value, Args... args) { cout << value << " "; showlist(args...); } int main() { showlist(1); showlist(1,1.1); showlist(1, 1.1,string("hello world")); return 0; }
递归函数参数T value, Args... args;第一个参数接受传递的第一个参数,第二个参数接受剩余的剩余的传递参数;通过子递归函数将参数打印出来
逗号表达式展开参数包
仅供了解即可
template<class T> void printarg(T t) { cout << t << " "; } template<class ...Args> void showlist(Args... args) { int arr[] = { (printarg(args),0)... }; cout << endl; } int main() { showlist(1); showlist(1, 1.1); showlist(1, 1.1, string("hello world")); return 0; }
STL容器中的emplace
相关接口函数
template<class ...Args> void emplace_back(Args&&... args);
可以观察到容器list的emplace_back接口支持模板的可变参数;接下来就探索此接口有什么优点
int main() { pair<int, yjm::string>kv(20, "sort"); std::list<std::pair<int, yjm::string>>lt; lt.emplace_back(kv);//左值 lt.emplace_back(make_pair(20, "sort"));//右值 lt.emplace_back(10, "sort");//构造pair参数包 cout << endl; lt.push_back(kv);//左值 lt.push_back(make_pair(30, "sort"));//右值 lt.push_back({ 40, "sort" });//右值 return 0; }
由运行结果来看,emplace_back接口减少了拷贝提高效率