zoukankan      html  css  js  c++  java
  • C++11右值引用与移动构造函数

    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是临时对象,作为右值处理。

     std::move

    #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] = '';
     }
    public:
     MyString() {
       _data = NULL;
       _len = 0;
       std::cout << "Default Constructor" << std::endl;
     }
    
     MyString(const char* p) {
       _len = strlen (p);
       _init_data(p);
       std::cout << "Constructor with: " << p << std::endl;
     }
    
    MyString(MyString&& str) {
       std::cout << "Move Constructor is called! source: " << str._data << std::endl;
       _len = str._len;
       _data = str._data;
       str._len = 0;
       str._data = NULL;
    }
    
    MyString& operator=(MyString&& str) {
       std::cout << "Move Assignment is called! source: " << str._data << std::endl;
       if (this != &str) {
         _len = str._len;
         _data = str._data;
         str._len = 0;
         str._data = NULL;  //赋值为null
       }
       return *this;
    }
    
    virtual ~MyString() {
       if (_data) free(_data); //等于NULL就不需要free
     }
    };
    
    int main() {
     MyString a;
     a = MyString("Hello"); //临时对象
     std::cout << "-----------------" << std::endl;
     //MyString b = MyString("Test"); //注意与a输出的结果的差异
     //std::cout << "-----------------" << std::endl;
    
     //std::vector<MyString> vec; 
     //vec.push_back(MyString("World")); 
    }
    root@ubuntu:~/c++# g++ -std=c++11  move2.cpp -o move
    root@ubuntu:~/c++# ./move
    Default Constructor
    Constructor with: Hello
    Move Assignment is called! source: Hello
    -----------------
    root@ubuntu:~/c++# 
    #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] = '';
     }
    public:
     MyString() {
       _data = NULL;
       _len = 0;
       std::cout << "Default Constructor" << std::endl;
     }
    
     MyString(const char* p) {
       _len = strlen (p);
       _init_data(p);
       std::cout << "Constructor with: " << p << std::endl;
     }
    
    MyString(MyString&& str) {
       std::cout << "Move Constructor is called! source: " << str._data << std::endl;
       _len = str._len;
       _data = str._data;
       str._len = 0;
       str._data = NULL;
    }
    
    MyString& operator=(MyString&& str) {
       std::cout << "Move Assignment is called! source: " << str._data << std::endl;
       if (this != &str) {
         _len = str._len;
         _data = str._data;
         str._len = 0;
         str._data = NULL;
       }
       return *this;
    }
    
    virtual ~MyString() {
       if (_data) free(_data);
     }
    };
    
    int main() {
     MyString a;
     std::cout << "-----------------" << std::endl;
     MyString b = MyString("Test"); //没有调move 构造函数
     std::cout << "-----------------" << std::endl;
    
    }
    root@ubuntu:~/c++# ./move
    Default Constructor
    -----------------
    Constructor with: Test
    -----------------
    #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] = '';
     }
    public:
     MyString() {
       _data = NULL;
       _len = 0;
       std::cout << "Default Constructor" << std::endl;
     }
    
     MyString(const char* p) {
       _len = strlen (p);
       _init_data(p);
       std::cout << "Constructor with: " << p << std::endl;
     }
    
    MyString(MyString&& str) {
       std::cout << "Move Constructor is called! source: " << str._data << std::endl;
       _len = str._len;
       _data = str._data;
       str._len = 0;
       str._data = NULL;
    }
    
    MyString& operator=(MyString&& str) {
       std::cout << "Move Assignment is called! source: " << str._data << std::endl;
       if (this != &str) {
         _len = str._len;
         _data = str._data;
         str._len = 0;
         str._data = NULL;
       }
       return *this;
    }
    
    virtual ~MyString() {
       if (_data) free(_data);
     }
    };
    
    int main() {
     MyString b("Test"); //注意与a输出的结果的差异
     MyString a = b;
     std::cout << "-----------------" << std::endl;
    
    }
    root@ubuntu:~/c++# g++ -std=c++11  move2.cpp -o move
    move2.cpp: In function ‘int main()’:
    move2.cpp:55:15: error: use of deleted function ‘constexpr MyString::MyString(const MyString&)’
      MyString a = b;
                   ^
    move2.cpp:7:7: note: ‘constexpr MyString::MyString(const MyString&)’ is implicitly declared as deleted because ‘MyString’ declares a move constructor or move assignment operator
     class MyString { 
           ^
    root@ubuntu:~/c++#
    #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] = '';
     }
    public:
     MyString() {
       _data = NULL;
       _len = 0;
       std::cout << "Default Constructor" << std::endl;
     }
    
     MyString(const char* p) {
       _len = strlen (p);
       _init_data(p);
       std::cout << "Constructor with: " << p << std::endl;
     }
    
    MyString(MyString&& str) {
       std::cout << "Move Constructor is called! source: " << str._data << std::endl;
       _len = str._len;
       _data = str._data;
       str._len = 0;
       str._data = NULL;
    }
    
    MyString& operator=(MyString&& str) {
       std::cout << "Move Assignment is called! source: " << str._data << std::endl;
       if (this != &str) {
         _len = str._len;
         _data = str._data;
         str._len = 0;
         str._data = NULL;
       }
       return *this;
    }
    
    virtual ~MyString() {
       if (_data) free(_data);
     }
    };
    
    int main() {
     MyString b("Test"); //注意与a输出的结果的差异
     MyString a = move(b);
     std::cout << "-----------------" << std::endl;
    
    }
    root@ubuntu:~/c++# ./move
    Constructor with: Test
    Move Constructor is called! source: Test
    -----------------
    root@ubuntu:~/c++# 
    #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] = ''; 
     } 
    public: 
     MyString() { 
       _data = NULL; 
       _len = 0; 
       std::cout << "Default Constructor" << std::endl;
     } 
     
     MyString(const char* p) { 
       _len = strlen (p); 
       _init_data(p); 
       std::cout << "Constructor with: " << p << std::endl;
     } 
     
    MyString(MyString&& str) { 
       std::cout << "Move Constructor is called! source: " << str._data << std::endl; 
       _len = str._len; 
       _data = str._data; 
       str._len = 0; 
       str._data = NULL; 
    }
        
    MyString& operator=(MyString&& str) { 
       std::cout << "Move Assignment is called! source: " << str._data << std::endl; 
       if (this != &str) { 
         _len = str._len; 
         _data = str._data; 
         str._len = 0; 
         str._data = NULL; 
       } 
       return *this; 
    }
       
    virtual ~MyString() { 
       if (_data) free(_data); 
     } 
    }; 
     
    int main() { 
     MyString a; 
     a = MyString("Hello"); 
     //std::move(a); 
     std::cout << "-----------------" << std::endl;
     MyString b = MyString("Test"); //注意与a输出的结果的差异
     std::cout << "-----------------" << std::endl;
     
     std::vector<MyString> vec; 
     vec.push_back(MyString("World")); 
    }
    root@ubuntu:~/c++#  g++ -std=c++11 obj.cpp  -o obj
    root@ubuntu:~/c++# ./obj 
    Default Constructor
    Constructor with: Hello
    Move Assignment is called! source: Hello
    -----------------
    Constructor with: Test
    -----------------
    Constructor with: World
    Move Constructor is called! source: World

    上述代码中,MyString("Hello")和MyString("World")都是临时对象,也就是右值;因此C++对右值调用了移动构造函数和移动赋值操作符,避免资源的创建、复制和销毁。

    同时,注意到对象a和b两种逻辑等价的写法,导致了完全不同的行为。参考后面的文章《再探C++对象构造》。

    移动构造函数和移动赋值操作符的实现需要注意以下几点:

    • 右值参数的符号必须是右值引用符号&&
    • 右值参数不能是const,因为我们需要修改右值
    • 右值参数的资源链接和标记必须修改,否则右值的析构函数会释放资源,转移到新对象的资源也就无效了

    在设计实现需要大量分配和释放资源的类是,应该考虑使用移动构造函数和移动赋值操作符来提供效率。

    #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] = ''; 
     } 
    public: 
     MyString() { 
       _data = NULL; 
       _len = 0; 
       std::cout << "Default Constructor" << std::endl;
     } 
     
     MyString(const char* p) { 
       _len = strlen (p); 
       _init_data(p); 
       std::cout << "Constructor with: " << p << std::endl;
     } 
     
    MyString(MyString&& str) { 
       std::cout << "Move Constructor is called! source: " << str._data << std::endl; 
       _len = str._len; 
       _data = str._data; 
       str._len = 0; 
       str._data = NULL; 
    }
        
    MyString& operator=(MyString&& str) { 
       std::cout << "Move Assignment is called! source: " << str._data << std::endl; 
       if (this != &str) { 
         _len = str._len; 
         _data = str._data; 
         str._len = 0; 
         str._data = NULL; 
       } 
       return *this; 
    }
       
    virtual ~MyString() { 
       if (_data) free(_data); 
     } 
    }; 
     
    int main() { 
     MyString a; 
     a = MyString("Hello"); 
     MyString(std::move(a)); 
    }
    root@ubuntu:~/c++#  g++ -std=c++11 obj.cpp  -o obj
    root@ubuntu:~/c++# ./obj 
    Default Constructor
    Constructor with: Hello
    Move Assignment is called! source: Hello
    Move Constructor is called! source: Hello
    int main() {
     MyString a;
     a = MyString("Hello");
     std::move(a);            
     //MyString(std::move(a)); 
    }
    root@ubuntu:~/c++# ./obj 
    Default Constructor
    Constructor with: Hello
    Move Assignment is called! source: Hello
    root@ubuntu:~/c++#

    注意点

    另外,C++编译器对很多需要调用移动构造函数的情况进行优化,也就是返回值优化,通常发生在当函数返回值被用于初始化一个对象时。这种情况下,移动构造函数不会被调用,会变为直接调用构造函数。

    Compilers already optimize many cases that formally require a move-construction call in what is known as Return Value Optimization. Most notably, when the value returned by a function is used to initialize an object. In these cases, the move constructor may actually never get called.

    还需要注意,右值引用很少用于移动构造函数之外的其他地方,非必要的使用会增加程序debug的难度。

  • 相关阅读:
    JavaScript初学者应注意的七个细节
    8个高质量图标的最佳搜索引擎
    Adobe CS5 For Mac综合贴(2011/01/22更新)
    http://apps.hi.baidu.com/share/detail/18571966
    不要使用@import
    【leetcode】Search in Rotated Sorted Array
    【leetcode】Excel Sheet Column Title
    C#Tcp多个客户端与服务器数据与文件传输
    唯一分解定理
    欧拉函数
  • 原文地址:https://www.cnblogs.com/dream397/p/14596187.html
Copyright © 2011-2022 走看看