首页 > 代码库 > C++11中右值引用和移动语义

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 = http://www.mamicode.com/\0;
        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 =http://www.mamicode.com/ rhs._data;
        rhs._data = nullptr;
    }

    MyString& operator=(MyString&& rhs){
        std::cout << "move assign " << std::endl;
        if(this->_data != rhs._data){
            this->_data =http://www.mamicode.com/ rhs._data;
            rhs._data = nullptr;
        }
        return *this;
    }
    ~MyString() {
        delete[] _data;
    }

    //assign
    MyString& operator=(const MyString& rhs) {
        std::cout << "assign" << std::endl;
        if (this->_data =http://www.mamicode.com/= rhs._data) {
            return *this;
        }
        delete[] this->_data;
        this->_data = http://www.mamicode.com/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可以提高性能。

参考: 

  • 条款24:区别开统一引用和右值引用

  • (原创)C++11改进我们的程序之右值引用

 

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