• C++11中右值引用和移动语义


    目录

      1. 左值、右值、左值引用、右值引用
      2. 右值引用和统一引用
      3. 使用右值引用,避免深拷贝,优化程序性能
      4. std::move()移动语义
      5. std::forward()完美转发
      6. 容器中的emplace_back()

    C++11增加了一个新的类型,称作右值引用(R-value reference),标记为T&&,右值引用结合std::move可以很好的优化程序的效率

    1.左值、右值、左值引用、右值引用

      左值是有名字的,对应了一定的内存区域,可访问;右值不具名,不对应内存域,不可访问,临时对像是右值。区分表达式的左右值属性有一个简便方法:若可对表达式用 & 符取址,则为左值,否则为右值。左值引用是对左值的引用,右值引用是对右值的引用。在C++11之前只存在左值引用,常量的引用是不合法的,在C++11中引入了右值引用。

    1 const int& cra = 1;
    2 int& ra = 1;//error
    3 int && a = 1; //&&为右值引用

    2.右值引用和统一引用

         “T&&”有两个不同的含义。一个当然是右值引用,这个引用表现出你所期望的:它们仅仅绑定到右值,它们的主要差事就是识别出那些可以被移动的对象。“T&&”另一个含义是既是右值引用,又是左值引用,称为统一引用。这样的引用在代码中看上去像右值引用(也就是T&&),但它们可以表现的像是左值引用。它们的双重特性使之可以既绑定到右值(像右值引用一样),也可以绑定到左值(像左值引用)。统一引用出现在两种场景下,最常见的是函数模板的参数,第二个场景是auto声名,两个场景共同之处是有类型推导的出现。在模板f中,param的类型是推导的,对var2的声明中,var2的类型也是推导的,如果想更详细了解&&,可以参考scott-meyers这个文章:http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

    1 void f(Widget&& param);               // 右值引用
    2 Widget&& var1 = Widget();             // 右值引用
    3 template<typename T>
    4 void f(std::vector<T>&& param);       // 右值引用
    5 template<typename T>
    6 void f(T&& param);                    // 统一引用
    7 auto&& var2 = var1;                   // 统一引用

    3.右值引用,避免深拷贝

         右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。消除了临时对象的维护 ( 创建和销毁 ) 对性能的影响。以一个简单的 string 类为示例,实现拷贝构造函数和拷贝赋值操作符。实现了调用拷贝构造函数的操作和拷贝赋值操作符的操作。MyString(“Hello”) 和 MyString(“World”) 都是临时对象,也就是右值。虽然它们是临时的,但程序仍然调用了拷贝构造和拷贝赋值,造成了没有意义的资源申请和释放的操作。如果能够直接使用临时对象已经申请的资源,既能节省资源,有能节省资源申请和释放的时间。这正是定义转移语义的目的。

    #include <iostream>
    #include <cstring>
    #include <vector>
    using namespace std;
    
    class MyString {
    private:
        char* _data;
    public:
        //default constructor
        MyString() :
                _data(new char[1]) {
            *_data = '';
            std::cout << "default constructor" << std::endl;
        }
        MyString(const char* str) :
                _data(new char[strlen(str) + 1]) {
            std::cout << "constructor" << std::endl;
            strcpy(_data, str);
        }
    
        //copy constructor
        MyString(const MyString& rhs) :
                _data(new char[rhs.size() + 1]) {
            std::cout << "copy constructor" << endl;
            strcpy(this->_data, rhs.c_str());
        }
    
        MyString(MyString&& rhs){
            std::cout << "move copy constructor" << std::endl;
            this->_data = rhs._data;
            rhs._data = nullptr;
        }
    
        MyString& operator=(MyString&& rhs){
            std::cout << "move assign " << std::endl;
            if(this->_data != rhs._data){
                this->_data = rhs._data;
                rhs._data = nullptr;
            }
            return *this;
        }
        ~MyString() {
            delete[] _data;
        }
    
        //assign
        MyString& operator=(const MyString& rhs) {
            std::cout << "assign" << std::endl;
            if (this->_data == rhs._data) {
                return *this;
            }
            delete[] this->_data;
            this->_data = new char[rhs.size() + 1];
            strcpy(this->_data, rhs._data);
            return *this;
        }
    
        bool operator==(const MyString& rhs) {
            if (strcmp(this->_data, rhs._data) == 0) {
                return true;
            } else {
                return false;
            }
        }
    
        char operator[](size_t index) {
            if (index >= strlen(_data)) {
                return '0';
            } else {
                return _data[index];
            }
        }
    
        friend MyString operator+(const MyString& lhs, const MyString& rhs) {
            MyString str;
            str._data = new char[lhs.size() + rhs.size() + 1];
            strcpy(str._data, lhs._data);
            strcat(str._data, rhs._data);
            return str;
        }
    
        friend ostream& operator<<(ostream &out, const MyString& str) {
            out << str._data;
            return out;
        }
    
        size_t size() const {
            return strlen(_data);
        }
    
        const char* c_str() const {
            return _data;
        }
    };
    int main() {
    
        MyString str1("hello");  //constructor
        MyString str2(str1);     //copy constructor
    
        MyString str3 = MyString("abc");  //constructor
        MyString str4 = std::move(MyString("aba")); //constructor,move copy constructor;
    
    
        str2 = std::move(str3);  //move assign
        str2 = MyString("abc");  //constructor,move assign
    
        vector<MyString> v;
        v.push_back(MyString("world")); //constructor,move copy constructor;
    
        return 0;
    }

    4.std::move()移动语义

           std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。这种移动语义是很有用的,比如我们一个对象中有一些指针资源或者动态数组,在对象的赋值或者拷贝时就不需要拷贝这些资源了。在c++11之前我们的拷贝构造函数和赋值函数可能要这样定义:假设一个A对象内部有一个资源m_ptr;

    A& A::operator=(const A& rhs)
    {
    // 销毁m_ptr指向的资源
    // 复制rhs.m_ptr所指的资源,并使m_ptr指向它
    }

      上面的过程是可行的,但是更有效率的办法是直接交换a和临时对象中的资源指针,然后让临时对象的析构函数去销毁a原来拥有的资源。换句话说,当赋值操作符的右边是右值的时候,我们希望赋值操作符被定义成下面这样:

    A& A::operator=(const A&& rhs)
    {
        // 仅仅转移资源的所有者,将资源的拥有者改为被赋值者
    
    }

     这就是所谓的move语义。再看一个例子,假设一个临时容器很大,赋值给另一个容器。

    {
        std::list< std::string > tokens;//省略初始化...
        std::list< std::string > t = tokens;
    }
    std::list< std::string > tokens;
    std::list< std::string > t = std::move(tokens);

    5.std::forword()完美转发

           右值引用类型是独立于值的,一个右值引用参数作为函数的形参,在函数内部再转发该参数的时候它已经变成一个左值了,并不是它原来的类型了。因此,我们需要一种方法能按照参数原来的类型转发到另一个函数,这种转发被称为完美转发。所谓完美转发(perfect forwarding),是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。C++11中提供了这样的一个函数std::forward,它是为转发而生的,它会按照参数本来的类型来转发出去,不管参数类型是T&&这种未定的引用类型还是明确的左值引用或者右值引用。

    template<typename T>
    void print(T& t) {
        std::cout << "lvalue" << std::endl;
    }
    
    template<typename T>
    void print(T&& t) {
        std::cout << "rvalue" << std::endl;
    }
    
    template<typename T>
    void testForward(T && t) {
        print(t);
        print(std::forward<T>(t));
        print(std::move(t));
    }

    6. 成员的emplace_back

     c++11中大部分容器都加了一个emplace_back成员函数,vector中它的定义是这样的:

    template< class... Args >
    void emplace_back( Args&&... args );

     这里的Args&&是一个未定的引用类型,因此它可以接收左值引用和右值引用,它的内部也是调用了std::forward实现完美转发的。因此如果我们需要往容器中添加右值、临时变量时,用emplace_back可以提高性能。

    参考: 

  • 相关阅读:
    RabbitMQ 路由选择 (Routing)
    RabbitMQ 发布/订阅
    RabbitMQ 工作队列
    MySQL中的insert ignore into, replace into等的一些用法总结
    BigDecimal用法详解
    RabbitMQ 入门 Helloworld
    git标签
    git查看提交历史
    RabbitMQ简介
    【计算机视觉】SeetaFace Engine开源C++人脸识别引擎
  • 原文地址:https://www.cnblogs.com/wxquare/p/6836271.html
Copyright © 2020-2023  润新知