C++11右值引用与移动构造函数  std::move

C++11标准新特性

右值引用(Rvalue Reference)是C++11标准引入的特性,它实现了转移语义和精确传递,主要的作用有2个方面:

  1. 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率;
  2. 能够更简洁明确地定义泛型函数;

左值引用和右值引用

首先区分什么是左值引用和右值引用,简单的定义:

  • 左值引用:就是命名对象,非临时对象,可以在多条代码中使用的对象;我们通常的变量都是左值;
  • 右值引用:就指非命名对象,临时对象,脱离当前代码语句就无法被引用;比如 函数的返回值;

从左值和右值的定义可以得到,左值引用的对象创建后在代码其他地方会被多次使用,其资源的生命周期较长。而右值引用的对象是临时的,通常用来初始化另一个对象;临时对象创建后很快就会被销毁,其创建-销毁造成了一次资源浪费。既然临时对象将永远不会在其他地方被使用,实际上使用临时对象的资源来初始化另一个对象并不需要一次Copy;更高效的做法将临时对象的资源转移(Move)到另一个命名对象,避免资源的Copy和浪费。

左值和右值的语法符号

C++11中左值的声明符号为&,右值的声明符号为&&

#include <iostream>
#include <utility>
#include <vector>
#include <string>
using namespace std;
void print_value(int& i) {
    cout<< "left_value called: "<<i<<endl;
}
void print_value(int&& i) {
    cout<< "right_value called: "<<i<<endl;
}
void print_value_2(int&& i) {
    cout<< "right_value called: "<<i<<endl;
}

int main() {
    int a=0;
    print_value(a);
    print_value(1);
    print_value_2(a);
}
root@ubuntu:~/c++#  g++ -std=c++11 move.c  -o move
move.c: In function ‘int main()’:
move.c:20:20: error: cannot bind ‘int’ lvalue to ‘int&&   print_value_2(a);
                    ^
move.c:12:6: note:   initializing argument 1 of ‘void print_value_2(int&&)’
 void print_value_2(int&& i) {
#include <iostream>
#include <utility>
#include <vector>
#include <string>
using namespace std;
void print_value(int& i) {
    cout<< "left_value called: "<<i<<endl;
}
void print_value(int&& i) {
    cout<< "right_value called: "<<i<<endl;
}
void print_value_2(int&& i) {
    cout<< "right_value called: "<<i<<endl;
}

int main() {
    int a=0;
    print_value(a);
    print_value(1);
    //print_value_2(a);
}
root@ubuntu:~/c++# ./move
left_value called: 0
right_value called: 1
root@ubuntu:~/c++# 

print_value函数被重载,分别接受左值和右值a是命名对象,作为左值处理;而1是临时对象,作为右值处理。

#include <iostream>
#include <utility>
#include <vector>
#include <string>
using namespace std;
void print_value(int& i) {
    cout<< "left_value called: "<<i<<endl;
}
void print_value(int&& i) {
    cout<< "right_value called: "<<i<<endl;
}
void print_value_2(int&& i) {
    cout<< "right_value called: "<<i<<endl;
}

int main() {
    int a=0;
    print_value(a);
    print_value(1);
    print_value_2(move(a));
}
root@ubuntu:~/c++# g++ -std=c++11  move.c -o move
root@ubuntu:~/c++# ./move
left_value called: 0
right_value called: 1
right_value called: 0
root@ubuntu:~/c++# 

转移语义

右值引用的主要目的是支持转移语义。转移语义可以将资源从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝及销毁,能够大幅度提升C++的性能。临时对象的维护对性能有严重影响。

在C++中,对象的拷贝通过定义拷贝构造函数和拷贝赋值操作符实现。要实现转移语义,需要定义移动构造函数和移动赋值操作符。

转移构造函数和转移赋值操作符

首先直观的来看下各种构造函数的调用情况。假设我们已经实现了类MyClass

MyClass fn();        // 返回MyClass对象的函数
MyClass foo;        // 调用默认的构造函数
MyClass bar = foo;    // 调用Copy构造函数
MyClass baz = fn();    // 调用Move构造函数
foo = bar;            // 调用Copy赋值操作符
baz = MyClass();    // 调用Move赋值操作符

函数fn返回的对象和MyClass()构造的对象都是非命名临时对象,这种情况下没有必要作Copy,将其资源转移到命名对象更高效;所以移动构造函数和移动赋值操作符被调用。

Move构造函数和Move赋值操作符接受自身类对象的右值引用作为参数,其定义如下:

MyClass (MyClass&&);            //Move Constructor
MyClass& operator= (MyClass&&);    //Move Assignment

示例程序如下:

 //std::move(a); 
#include <iostream>
#include <utility>
#include <vector>
#include <string>
#include <string.h>
using namespace std;
class MyString {
private:
 char* _data;
 size_t   _len;
 void _init_data(const char *s) {
   _data = new char[_len+1];
   memcpy(_data, s, _len);
   _data[_len] = '