c++之右值引用与移动语义

c++11新增了一个新的特性:移动语义,这是一个非常有用的特性,今天我们就来了解一下这个特性。

右值引用

首先看下什么是右值引用。右值可以简单理解为可作用于等号右边的一类数据,它们有一个重要的特征:可以取其值不可以取其地址。
比如说13这个数字,不能对它进行取地址操作,也就是不能&13.
常见的右值有:
1.字面常量(c风格字符串除外)
2.函数返回值
3.类似于x+y这种类型的表达式
所谓右值引用也就是对右值进行引用。通过&&操作符完成
比如int &&x = 13;
此时虽然不能取13的地址,但是可以取x的地址。将右值关联到引用使得数据与特定的地址关联。
又如int &&r2 = x+y;
r2引用的是x+y的返回值,即使以后修改了x或者y,也不会影响r2;
演示代码:

#include <iostream>
using namespace std;
inline double test(double tcf) { return 5.0*tcf/32.0;}

int main()
{
    double a = 21.4;
    int && r1 = 13;
    double && r2 = a * 2;
    double && r3 = test(a);
    double && r4 = r2 + r3;
    cout << "the r1's value and address: " << r1 << "," << &r1 << endl;
    cout << "the r2's value and address: " << r2 << "," << &r2 << endl;
    cout << "the r3's value and address: " << r3 << "," << &r3 << endl;
    cout << "the r4's value and address: " << r4 << "," << &r4 << endl;

}

在这里插入图片描述

移动语义

为什么需要移动语义,首先来看个问题

vector<string> vstr
//vstr里面包含2000个string对象
vector<string> vstr_copy1(vstr)
//复制vstr

这段代码怎么执行呢。首先编译器会为vstr_copy1分配2000个string对象的内存。接着对于vstr中的每个string对象都会调用复制构造函数。然后将vstr中2000个string对象一一复制到vstr_copy1中。这里的复制确实没问题,是必须的,但是还有一种情况:

vector<string> allcaps(const vector<string> & vs)
{
vector<string> temp;
//temp包含2000个string对象
return temp
}

vector<string> vstr_copy2(allcaps(vstr));

这里与之前的不同之处是,allcaps返回的对象是临时的,对其进行深度复制的话会重复做了许多无用功。这样的话不如直接将字符串留在原地,而将vstr_copy2与这些字符串进行关联。类似于计算机移动文件,文件还留在原地,只是修改了记录。这种方法就是移动语义。
实现移动语义的话,可以将右值引用作为函数形参。
下面是一个移动实例(来自c++prime plus)

#include <iostream>

using namespace std;

class Useless
{
private:
    int n;
    char *pc;
    static int ct;
    void ShowObject() const;
public:
    Useless();
    explicit Useless(int k);
    Useless(int k,char ch);
    Useless(const Useless &f);
    Useless(Useless && f);
    ~Useless();
    void ShowData() const;
    Useless operator+(const Useless & f) const;
    Useless & operator=(const Useless & f);
    Useless & operator=(Useless && f);

};

int Useless::ct = 0;

Useless::Useless()
{
    ++ct;
    n = 0;
    pc = nullptr;
    cout << "default constructor called; number of object:" << ct << endl;
    ShowObject();
}

Useless::Useless(int k) : n(k)
{
    ++ct;
    cout << "int constructor called; number of object:" << ct << endl;
    pc = new char[n];
    ShowObject();
}

Useless::Useless(int k, char ch) : n(k)
{
    ++ct;
    cout << "int,char constructor called; number of object:" << ct << endl;
    pc = new char[n];
    for(int i = 0;i < n;i++)
        pc[i] = ch;
    ShowObject();
}

Useless::Useless(const Useless& f) : n(f.n)
{
    ++ct;
    cout << "copy const constructor called; number of object:" << ct << endl;
    pc = new char[n];
    for(int i =0; i< n;i++)
        pc[i] = f.pc[i];
    ShowObject();
}

Useless::Useless(Useless && f) : n(f.n)
{
    ++ct;
    cout << "move constructor called; number of object:" << ct << endl;
    pc = f.pc;
    f.pc = nullptr;
    f.n = 0;
    ShowObject();
}

Useless::~Useless()
{
    cout << "destructor called; object left: " << --ct << endl;
    cout << "deleted object:\n";
    ShowObject();
    delete [] pc;

}

Useless Useless::operator+(const Useless & f) const
{
    cout << "entering operator+()\n";
    Useless temp = Useless(n + f.n);
    for(int i =0; i< n;i++)
        temp.pc[i] = pc[i];
    for(int i = n;i < temp.n;i++)
        temp.pc[i] = f.pc[i-n];
    cout << "temp object" << endl;
    cout << "leaving operator+" << endl;
    return temp;
}

void Useless::ShowObject() const
{
    cout << "number of elements:" << n;
    cout << "data address:" << (void *) pc << endl;

}

void Useless::ShowData() const
{
    if(n == 0)
        cout << "(obeject empty)";
    else
        for(int i = 0;i<n;i++)
            cout << pc[i];
    cout << endl;
}

Useless & Useless::operator=(const Useless & f)
{
    if(this == &f)
        return *this;
    delete [] pc;
    n = f.n;
    pc = new char[n];
    for(int i = 0;i<n;i++)
        pc[i] = f.pc[i];
    return *this;
}

Useless & Useless::operator=(Useless && f)
{
    if(this == &f)
        return *this;
    delete [] pc;
    n = f.n;
    pc = f.pc;
    f.n = 0;
    f.pc = nullptr;
    return *this;
}

测试代码如下

int main()
{
    {
        Useless one(10,'x');
        Useless two = one;
        Useless three(20,'o');
        Useless four (one + three);
        cout << "object one:" ;
        one.ShowData();
        cout << "object two:" ;
        two.ShowData();
        cout << "object three:" ;
        three.ShowData();
        cout << "object four:" ;
        four.ShowData();
    }
}

这里面其中Useless(Useless && f); 这个构造函数就采用了移动语义;
在测试代码里four对象的参数是一个函数返回值(也就是右值),于是它就匹配的移动语义的构造函数。
而two对象的话匹配的是复制构造函数。
所以我们可以预测,two对象与one对象的数据地址应该是不同的
而four对象的数据地址应该是跟(one+three)这个表达式返回的临时对象的数据地址是一样的,我们可以执行下看下输出。
在这里插入图片描述

需要比较的结果我已经圈出来,可以看到确实是符合预期。
如果实参为右值,const引用将指向一个临时变量
上述useless类的=运算符也实现了移动语义

总结一下:实际上移动语义应该可以由编译器判断完成,不过c++11新增这一特性将选择权给了程序员,善用移动语义可以减少冗余操作,提高程序执行速度。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值