8.5.2 应用rvalue引用形参
当源对象是一个临时对象,在复制操作之后立即就被销毁时,复制的替代方案是偷用由pmessage成员指向的临时对象的内存,并传送到目标对象。如果可以这么做,那么不需要为目标对象分配更多的内存,也不需要释放源对象拥有的内存。在操作完成之后将立即销毁源对象,因此这么做没有风险--只是加快了执行速度。实现此技术的关键是检测复制操作中的源对象何时是一个rvalue。这正是rvalue引用形参能做的事情。
可以像下面这样额外创建operator=()函数的重载:
- CMessage& operator=(CMessage&& aMess)
- {
- cout << "Move assignment operator function called." << endl;
- delete[] pmessage; // Release memory for left operand
- pmessage = aMess.pmessage; // Steal string from rhs object
- aMess.pmessage = nullptr; // Null rhs pointer
- return *this; // Return a reference to 1st operand
- }
当右操作数是一个rvalue,即临时对象时,调用此运算符函数。当右操作数是一个lvalue时,调用具有lvalue引用形参的原始函数。函数的rvalue引用版本删除目标对象的pmessage成员指向的字符串,并复制源对象的pmessage成员中存储的地址。然后将源对象的pmessage成员设置为nullptr。这么做是必需的,源对象的析构函数调用会删除消息。需要注意的是,在此例中,不能将形参指定为const,因为您正在修改它。
添加具有rvalue引用形参的复制构造函数的重载,可以将相同的逻辑应用于复制构造函数操作:
- CMessage(CMessage&& aMess)
- {
- cout << "Move copy constructor called." << endl;
- pmessage = aMess.pmessage;
- aMess.pmessage = nullptr;
- }
不是将源对象的消息复制到被构造的对象,取而代之的只是将消息字符串的地址从源对象传输到新对象,因此在此例中,复制只是一个移动操作。与以前一样,将源对象的pmessage设置为nullptr,以防止析构函数删除消息字符串。
试一试:高效的对象复制操作
创建一个新的控制台应用程序Ex8_08,并复制Ex8_07的代码。然后添加重载的operator=()函数,并将刚才讨论的构造函数复制到CMessage类定义中。此例子会产生如下输出:
- Constructor called.
- Constructor called.
- Constructor called.
- Executing: motto3 = motto1 + motto2
- Add operator function called.
- Constructor called.
- Move copy constructor called.
- Destructor called.
- Move assignment operator function called.
- Destructor called.
- Done!!
- Executing: motto3motto3 = motto3 + motto1 + motto2
- Add operator function called.
- Constructor called.
- Move copy constructor called.
- Destructor called.
- Add operator function called.
- Constructor called.
- Move copy constructor called.
- Destructor called.
- Move assignment operator function called.
- Destructor called.
- Destructor called.
- Done!!
- motto3 contains -
- The devil takes care of his own. If you sup with the devil use a long spoon.
- The devil takes care of his own. If you sup with the devil use a long spoon.
- Destructor called.
- Destructor called.
- Destructor called.
示例说明
从输出结果可以看到,上例中涉及对象复制的所有操作现在都执行为移动操作。与复制构造函数调用一样,赋值运算符函数调用现在使用的是具有rvalue引用形参的版本。输出结果还表明,motto3字符串的最后结果与以前相同,因此一切工作正常。
对于定义复杂对象的类,用具有rvalue引用形参的版本重载赋值运算符和复制构造函数这会大大地提高性能。
如果在类中定义operator=()函数和复制构造函数时,将形参定义为非常量rvalue引用,则需要确保也定义了具有const lvalue引用形参的标准版本。否则,编译器会提供它们的默认版本,逐一成员地进行复制。这肯定不是我们所期望的。