右值引用与转移语义(C++11)

参考资料:

http://www.cnblogs.com/lebronjames/p/3614773.html

 

左值和右值定义:

C++( 包括 C) 中所有的表达式和变量要么是左值,要么是右值。通俗的左值的定义就是非临时对象(可以取地址,有名字),那些可以在多条语句中使用的对象。所有的变量都满足这个定义,在多条代码中都可以使用,都是左值。右值是指临时的对象,它们只在当前的语句中有效。请看下列示例 :

    1. 简单的赋值语句


    如:int i = 0;


    在这条语句中,i 是左值,0 是临时值,就是右值。在下面的代码中,i 可以被引用,0 就不可以了。立即数都是右值。

     2. 右值也可以出现在赋值表达式的左边,但是不能作为赋值的对象,因为右值只在当前语句有效,赋值没有意义。

     如:((i>0) ? i : j) = 1;

     在这个例子中,0 作为右值出现在了”=”的左边。但是赋值对象是 i 或者 j,都是左值。

     在 C++11 之前,右值是不能被引用的,最大限度就是用常量引用绑定一个右值,如 :

     const int &a = 1;

 

左值和右值的语法符号:

      左值的声明符号为”&”, 为了和左值区分,右值的声明符号为”&&”。但是如果临时对象通过一个接受右值的函数传递给另一个函数时,就会变成左值,因为这个临时对象在传递过程中,变成了命名对象。

#include <iostream>
using namespace std;

void value(int& v)
{
    cout<<"left ";
    cout<<__func__<<hex<<":"<<v<<endl;
}

void value(int&& v)
{
    cout<<"right ";
    cout<<__func__<<hex<<":"<<v<<endl;
}

void fvalue(int&& v)
{
    value(v);
}

int main()
{
    int a=12;
    value(a);
    value(2);
    fvalue(1);

    value(std::move(a));
}

 

转移语义:

     右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。

    通过转移语义,临时对象中的资源能够转移其它的对象里。

    在现有的 C++ 机制中,我们可以定义拷贝构造函数和赋值函数。要实现转移语义,需要定义转移构造函数,还可以定义转移赋值操作符。对于右值的拷贝和赋值会调用转移构造函数和转移赋值操作符。如果转移构造函数和转移拷贝操作符没有定义,那么就遵循现有的机制,拷贝构造函数和赋值操作符会被调用。

示例:

#include <cassert>
#include <iostream>
#include <cstring>

using namespace std;

class String
{
    private :
    char *_data;
    int _len;

    void init(const char* s)
    {
        _data=new char[_len+1];
        memcpy(_data,s,_len);
        _data[_len]='';
    }

    public:
    String():_data(nullptr),_len(0){}
    String(const char* str)
    {
        assert(str!=nullptr);
        _len=strlen(str);
        init(str);
    }

    String(const String& str)
    {
        cout<<"call cctor"<<endl;
        _len=str._len;
        init(str._data);
    }

    String& operator=(const String& str)
    {
        cout<<"call copy assignment"<<endl;
        if(this!=&str)
        {
        _len=str._len;
        init(str._data);
        }
        return *this;
    }

    //转移构造函数(C++11)
    String(String&& str)
    {
        cout<<"call move ctor"<<endl;
        _len=str._len;
        _data=str._data;
        str._data=nullptr;
        str._len=0;
    }

    //转移赋值操作符(C++11)
    String& operator=(String&& str)
    {
        cout<<"call move assign"<<endl;
        if(this!=&str)
        {
        _len=str._len;
        _data=str._data;
        }
        return *this;
    }

    void print()
    {
        cout<<"data:"<<hex<<_data<<endl;
    }

    ~String()
    {
        cout<<"dctor"<<endl;
        if(_data)
        {
        delete[] _data;
        }
    }
};


int main()
{
    const char* s="hello world";
    String str(s);
    str.print();

    String tmp(String("hello"));
    tmp.print();

    String&& mstr=std::move(str);
    tmp=mstr;
    tmp.print();
}

编译选项:g++ move.cpp -std=c++11 -fno-elide-constructors

其中-fno-elide-constructors 意思是:强制g++总是调用copy构造函数,即使在用临时对象初始化另一个同类型对象的时候。

 

标准库函数std::move

       既然编译器只对右值引用才能调用转移构造函数和转移赋值函数,而所有命名对象都只能是左值引用,如果已知一个命名对象不再被使用而想对它调用转移构造函数和转移赋值函数,也就是把一个左值引用当做右值引用来使用,怎么做呢?标准库提供了函数 std::move,这个函数以非常简单的方式将左值引用转换为右值引用。

示例:

#include<iostream>
using namespace std;

template<class T>
void m_swap(T& a,T& b)
{
    T tmp(std::move(a));
    a=std::move(b);
    b=std::move(tmp);
}

int main()
{
    int a=0;
    int b=1;
    cout<<"before swap:"<<a<<" "<<b<<endl;

    m_swap(a,b);
    cout<<"after swap:"<<a<<" "<<b<<endl;
}
原文地址:https://www.cnblogs.com/luosongchao/p/4104604.html