C++11新特性---右值引用(二)

本文摘自博客:https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/index.html

1、新特性的目的

  1. 右值引用是C++11中引入的新特性,它实现了转移语义和精确传递
  2. 消除两个对象交互式不必要的对象拷贝,节省存储资源,提高效率;

2、左值和右值的定义

  1. 左值是非临时对象,可以取地址,可以被赋值;右值是临时对象,只在当前语句有效。不能被取地址和赋值。
  2. 如果右值出现在表达式的左边,那么他不能作为赋值的对象。如(i>0)?i:j = 1。
  3. 在C++11之前,右值不能被引用,最大限度就是用常量引用绑定一个右值,如const int &a=1。在这种情况下右值不能被修改。

3、左值和右值的语法符号

左值的申明符号为&,右值的申明符号为&&

//运行结果 //LValue processed: 0
//RValue processed: 1 
void process_value(int& i)
{
     std::cout << "LValue processed: " << i << std::endl;
}
void process_value(int&& i)
{
    std::cout << "RValue processed: " << i << std::endl;
}
int main()
{
     int a = 0;
     process_value(a);
     process_value(1);
}

process_value函数被重载,分别接受左值和右值。由输出结果可以看出,临时对象被作为右值处理的。

但是如果临时对象通过一个接受右值的函数传递另一个参数时,就会变成左值,因为这个临时对象在传递过程中,变成了命名对象。

//运行结果
//LValue processed: 0
//RValue processed: 1
//LValue processed: 2
void process_value(int& i)
{
    std::cout << "LValue processed: " << i << std::endl;
}
void process_value(int&& i)
{
    std::cout << "RValue processed: " << i << std::endl;
}
void forward_value(int&& i)
{    process_value(i);

}
int main()
{
    int a = 0;
    process_value(a);
    process_value(1);
    forward_value(2);
}

虽然 2 这个立即数在函数 forward_value 接收时是右值,但到了 process_value 接收时,变成了左值。

 

4、转移语句的定义

  1. 右值引用是用来支持转移语义的。转移语义可以将资源(堆、系统对象等)从一个对象转移到另一个对象,这样能减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高性能,临时对象的维护(创建和销毁)对性能有严重影响。
  2. 通过转移语义,临时对象的资源可以转移到其他对象中
  3. 要实现转移语义,需要定义转移构造函数,还可以定义转移赋值操作符。对于右值的拷贝和复制会调用转移构造函数和转移赋值操作符。如果两个函数没有定义,那么就调用现有的拷贝构造函数和赋值操作符。
  4. 普通的函数和操作符也可以利用右值操作符实现转移语义。

以一个简单的 string 类为示例,实现拷贝构造函数和拷贝赋值操作符。

class MyString
{
private:
    char* _data;
    int   _len;
    void _init_data(const char *s)
    {
        _data = new char[_len + 1];
        memcpy(_data, s, _len);
        _data[_len] = '\0';
    }
public:
    MyString()
    {
        _data = NULL;
        _len = 0;
    }
    MyString(const char* p)
    {
        _len = strlen(p);
        _init_data(p);
    }
    MyString(const MyString& str)
    {
        cout<<"MyString(const &)"<<endl;
        _len = str._len;
        _init_data(str._data);
    }
    MyString& operator=(const MyString& str)
    {
        cout<<"operator=(const &)"<<endl;
        if (this != &str)
        {
            _len = str._len;
            _init_data(str._data);
        }
        return *this;
    }
    virtual ~MyString()
    {
        if (_data)
            free(_data);
    }
};
int main()
{
    MyString a;
    a = MyString("Hello");
    std::vector<MyString> vec;
    vec.push_back(MyString("World"));
}

运行结果:

在main()函数中,实现了调用赋值操作符的操作和拷贝构造函数。Mystring(“hello”)和MyString(“World”)都是临时对象,也就是右值。虽然它们是临时的,但是程序还是会调用拷贝构造函数和赋值操作符函数,造成了没有意义的资源申请和释放操作。定义转移语义的目的就是希望直接使用临时对象已经申请的资源,这样既能节省资源,又能节省资源申请和释放的时间。

定义转移构造函数

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;
}

运行结果:

由此可以看出,编译器区分了左值和右值,对右值调用了转移构造函数和转移赋值操作符,节省了资源,提高了程序运行效率。故我们在设计类时,如果需要动态申请大量资源,我们应该设计转移构造函数和转移赋值操作符,以提高效率。

 

5、move函数

编译器只对右值引用才调用转移构造函数和转移赋值函数,但是所用命名对象都是左值引用,如果想把一个左值引用当作右值引用来使用,就可以使用move函数将左值引用转换成右值引用。
 

//运行结果
//LValue processed: 0
//RValue processed: 0
void ProcessValue(int& i)
{
    std::cout << "LValue processed: " << i << std::endl;
}
void ProcessValue(int&& i)
{
    std::cout << "RValue processed: " << i << std::endl;
}
int main()
{
    int a = 0;
    ProcessValue(a);
    ProcessValue(std::move(a));
}

move函数可以移动某资源到另一个对象,而不是拷贝一份后复制给新对象,就像剪切操作和复制操作一样。

 

6、精确传递

精确传递适用于这样的场景:需要将一组函数原封不动的传递给另一个函数。

原封不动指的是不仅参数的值不变,而且属性也不变(左值还是右值)

精确传递在泛型函数中,需求比较普遍。下面举一个例子,函数forward_value是一个泛型函数,它将一个参数传递给另一个函数process_value。

Forward_value的定义为:

template <typename T> void forward_value(const T& val)
{
    process_value(val);
}
template <typename T> void forward_value(T& val)
{
    process_value(val);
}

函数forward_value为每一个参数必须重载两种类型,T&和const T&,否则,下面四种不同类型参数的调用就不能同时满足:

int a = 0;
const int &b = 1;
forward_value(a); // int&
forward_value(b); // const int&
forward_value(2); // ----int&

对于一个参数就要重载两次,就是说函数重载和参数个数成正比,这样来说是非常低效的。看看右值引用如何解决

template <typename T> void forward_value(T&& val)
{
    process_value(val);
}

只定义一次,接收一个右值引用的参数,就能将所有的参数类型原封不动的传递给目标函数。四种不同类型参数的调用都能满足,参数的左右值属性和const属性都完全传递给目标函数process_value。

int a = 0;
const int &b = 1;
forward_value(a); // int&
forward_value(b); // const int&
forward_value(2); // int&&

C++11中定义的T&&的推导规则为:右值实参为右值引用,左值实参仍然为左值引用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值