8.8 完美转发(1)
完美转发是一个重要的概念,因为它能显著提升大对象的性能,这些大对象会花很多时间进行复制或创建。完美转发仅与类模板或函数模板的环境相关。初看起来它似乎有点复杂,但一旦掌握了这个概念,它就会变得非常简单。那么什么是完美转发呢?
假定有一个函数fun1(),它用一个类类型T来参数化。这可以用一个函数模板来定义,或者在类模板中定义。再假定将fun1()定义为带一个T&&类型的rvalue引用形参。如第6章所述,fun1()可以用一个lvalue、lvalue引用或rvalue实参来调用。Lvalue或lvalue引用实参会使fun1()模板实例有一个lvalue引用形参,否则它就有一个rvalue引用形参。
接着假定fun1()调用了另一个函数fun2(),fun2()有两个版本,一个版本有一个lvalue引用形参,另一个版本有一个rvalue引用形参。fun1()把它接收的实参传送给fun2()。理想情况下,当fun1()接收的实参是一个rvalue引用时,它就应调用有rvalue引用形参的fun2()版本,这样初始参数就不会移动或复制。当实例化fun1()并用lvalue或lvalue引用实参调用时,它就应调用有lvalue引用形参的fun2()版本。换言之,fun1()的实参要进行完美转发使代码总保持最佳性能。
当实参是rvalue时,需要将fun1()中的形参引用从lvalue转换为rvalue的方式,这样就可以把它作为rvalue传送给fun2()。于是该实参就不会复制或移动。fun1()的实参是lvalue或lvalue引用时,它就应保持不变,就像在对fun2()的调用中一样。这就是utility头文件中声明的std::forward()函数模板的作用。如果给它传送一个rvalue引用实参,它就把该实参返回为rvalue。如果给它传送一个lvalue引用,它就把该实参返回为一个lvalue引用。下面看看其工作方式。
试一试:完美转发
这个例子使用本章末尾详细讨论的string类。除了演示完美转发之外,该例子还演示了如何把模板函数定义为非模板类的一个成员,以及如何使用带两个类型形参的模板。该示例定义的Person类如下所示:
- class Person
- {
- public:
- // Constructor template
- template<class T1, class T2>
- Person(T1&& first, T2&& second) :
- firstname(forward<T1>(first)), secondname(forward<T2>(second)) {}
- // firstname(first), secondname(second) {}
- string getName() const
- {
- return firstname.getName() + " " + secondname.getName();
- }
- private:
- Name firstname;
- Name secondname;
- };
这是一个普通的类,其构造函数由一个带两个类型形参T1和T2的模板来定义,这样构造函数的实参就可以是不同的类型,例如string类型和char*类型。存储一个人的姓名的数据成员是Name类型,这将稍后定义。Person构造函数用实参初始化数据成员之前,把它们传送给utility头文件中的std::forward()。这将确保rvalue引用实参用于初始化Person类的数据成员时仍是rvalue引用。后面会注释掉这一行代码。getName()成员返回Person对象的字符串表示。
下面是Name类的定义:
- class Name
- {
- public:
- Name(string& aName) : name(aName)
- {
- cout << "Lvalue Name constructor." << endl;
- }
- Name(string&& aName) : name(move(aName))
- {
- cout << "Rvalue Name constructor." << endl;
- }
- const string& getName() const { return name; }
- private:
- string name;
- };
这个类把一个名字封装为一个string对象。string对象可以从以空字符结尾的字符串或另一个string对象中创建。该类有两个构造函数,一个带lvalue引用实参,另一个带rvalue引用实参,后者仅在实参是rvalue引用时调用。移动rvalue引用实参的原因是string类支持移动语义。