上节我们提出了右值引用,可以用来区分右值,那么这有什么用处?

 

问题来源

 

我们先看一个C++中被人诟病已久的问题:

我把某文件的内容读取到vector中,用函数如何封装?

大部分人的做法是:

void readFile(const string &filename, vector<string> &words)
{
words.clear();
//read XXXXX
}

这种做法完全可行,但是代码风格谈不上美观,我们尝试着这样编写:

vector<string> readFile(const string &filename)
{
vector<string> ret;
ret.push("cesfwfgw"); //....
// return ret;
}

这样我们就可以在main中这样调用:

vector<string> coll = readFile("fef.text");

但是,稍微熟悉C++的都知道,这样在语法上会造成大量的开销:

ret复制给临时变量,该临时变量开辟在heap上

临时变量复制给coll

这中间产生两次复制和销毁的开销。

如果说这个例子,可以采用开头的代码解决开销,那么如果是一个查询返回结果的函数,那么我们必须这样写:

vector<string> queryWord(const string &word)
{
vector<string> result;
//XXXXX return result;
}

这里的开销就无法避免了。

 

移动语义的引入

 

我们考虑一个生活中常见的问题(这里参考了如何评价 C++11 的右值引用(Rvalue reference)特性?),如果把一个很重的货物从箱子A移动到箱子B,那么

正常的做法是:打开箱子A,把物品搬出来,移动到B,然后关上A。

另一种比较奇葩的做法是:在B中复制一个物品A,然后将A中的销毁。

更奇葩的做法是:由于复制工具的局限性,我们无法直接在B中复制,所以我们只好先在地上复制一个物品temp,销毁A中的物品,然后根据temp在B中再复制一份,再销毁地上的temp。

事实上,C++98采用的就是最后一种效率极其低下的做法,这里的关键在于,C++98没有很好的区分“copy”和“move”语义。

上述问题中,我们明确提出移动A到B中,但是C++98由于移动语义的缺失,只好采用copy的方式完成。

 

我们再回到开头的问题中:

vector<string> readFile(const string &filename)
{
vector<string> ret;
ret.push("cesfwfgw"); //....
// return ret;
}

这里我们必须看到一点,在完成函数调用后,ret就要被销毁,所以我们想到一个主意,不是把ret中的内容复制给main中的coll,而是将ret中的内容偷到coll中,然后将ret悄悄的销毁。

这样是可行的,因为ret的生命周期很短。

 

哪些可以偷?

 

现在问题来了,C++中哪些可以偷,哪些不能?

我们回顾上一节提到的四个表达式:

string one("one");
const string two("two");
string three() { return "three"; }
const string four() { return "four"; }

显然,one和two生命周期较长,不能偷。four具有const属性,拒绝被偷。

那么three是可以被偷取的,因为它是临时变量,又没有const属性。

所以,C++中的非const右值,和移动语义完全匹配。

上节我们提出用右值引用区分右值,正是为了解决哪些可以偷的问题!

 

OK,我们的思路已经很清晰了:

1.为了解决返回对象开销问题,我们提出“偷取”,而不是复制对象

2.我们面临哪些能偷,哪些不能偷的问题。

3.右值可以偷取,所以我们如何区分右值?

4.我们引入右值引用X &&来区分右值。

这就是右值引用的来源。

 

如果一个变量不是右值,但是我们又需要偷取,那么我们可以采用std::move函数,将其强制转化为右值引用。

例如:

void test(string name)
{
string temp(std::move(name));
// XXXXXX
}

注意,被偷取之后的name无法继续使用,所以move函数不可以随意使用。

 

带来的影响

 

那么,右值引用带来哪些该变呢?

首先是类的成员函数赋值,看下面代码:

class People
{
public:
People()
{
cout << "People()" << endl;
}
People(const string &name)
: name_(name)
{
cout << "People(const string &name)" << endl;
}
People(string &&name)
: name_(name)
{
cout << "People(string &&name)" << endl;
} private:
string name_;
};

这里name赋值,我们相对于C++98,提供了一个右值函数,将name的值移动给name_。

事实上,上面的两个函数可以合成一个:

class People
{
public:
People()
{
cout << "People()" << endl;
} People(string name)
: name_(std::move(name))
{
cout << "People(string name)" << endl;
} private:
string name_;
};

这里注意,上面的name采用传值,并没有带来开销,因为:

如果name传入的是一个右值,那么name本身采用移动构造,开销比复制小很多,相当于People(string &&name)

如果name传入的其他值,那么name是复制构造,然后移动给name_,也没有增加额外的开销。
对于构造函数,除了提供复制构造函数,还需要移动构造函数。如下:
class People
{
public:
People()
{
cout << "People()" << endl;
} People(string name)
: name_(std::move(name))
{
cout << "People(string name)" << endl;
} People(const People &p)
: name_(p.name_)
{
cout << "People(const People &p)" << endl;
}
People(People &&p)
: name_(std::move(p.name_))
{
cout << "People(People &&p)" << endl;
} private:
string name_;
};

注意在最后一个People(People &&p)中,移动p内的name时,必须显式使用move,来强制移动name成员。

同样,还有移动赋值运算符。

另外,在C++98中,容器内的元素必须具备值语义,现在则不同,元素具备移动能力即可,后文我们在智能指针系列会提到unique_ptr,它可以放入vector中,但是不具有复制和赋值能力。

其他的影响请参考:如何评价 C++11 的右值引用(Rvalue reference)特性?

下文通过一个string的模拟实现,演示右值引用的使用。

C++11之右值引用(二):右值引用与移动语义的更多相关文章

  1. C++11常用特性介绍——左值引用、右值引用

    一.左值.右值 1)左值:可以放在赋值号左侧.可以被赋值的值:左值必须要在内存中有实体. 2)右值:必须放在赋值号右侧.取出值赋值给其它变量:右值可以在内存中也可以在CPU寄存器中. 二.引用 引用是 ...

  2. c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用

    为什么要用移动语义 先看看下面的代码 // rvalue_reference.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #includ ...

  3. C++ 11 左值,右值,左值引用,右值引用,std::move, std::foward

    这篇文章要介绍的内容和标题一致,关于C++ 11中的这几个特性网上介绍的文章很多,看了一些之后想把几个比较关键的点总结记录一下,文章比较长.给出了很多代码示例,都是编译运行测试过的,希望能用这些帮助理 ...

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

    本次主要讲c++11中的右值引用,后面还会讲到右值引用如何结合std::move优化我们的程序. c++11增加了一个新的类型,称作右值引用(R-value reference),标记为T & ...

  5. c++11 左值引用、右值引用

    c++11 左值引用.右值引用 #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #i ...

  6. 左值与右值,左值引用与右值引用(C++11)

    右值引用是解决语义支持提出的 这篇文章要介绍的内容和标题一致,关于C++ 11中的这几个特性网上介绍的文章很多,看了一些之后想把几个比较关键的点总结记录一下,文章比较长.给出了很多代码示例,都是编译运 ...

  7. C++11新特性(1) 右值引用

    在C++中,左值(lvalue)是能够获取其地址的一个量.因为常常出如今赋值语句的左边.因此称之为左值.比如一个有名称的变量. 比如: int a=10; //a就是一个左值. 传统的C++引用,都是 ...

  8. C++11的左值引用与右值引用总结

    概念 在C++11中,区别表达式是左值或右值可以做这样的总结:当一个对象被用作右值的时候,用的是对象的值(内容):当对象被用作左值的时候,用的是对象的身份(在内存中的位置).左值有持久的状态,而右值要 ...

  9. C++11左值引用和右值引用

    转载:https://www.cnblogs.com/golaxy/p/9212897.html C++11的左值引用与右值引用总结 概念 1.&与&&  对于在C++中,大家 ...

  10. 【C/C++开发】C++11:右值引用和转发型引用

    右值引用 为了解决移动语义及完美转发问题,C++11标准引入了右值引用(rvalue reference)这一重要的新概念.右值引用采用T&&这一语法形式,比传统的引用T&(如 ...

随机推荐

  1. 洛谷T8116 密码

    T8116 密码 题目描述 YJC把核弹发射密码忘掉了……其实是密码被加密了,但是YJC不会解密.密码由n个数字组成,第i个数字被加密成了如下形式:第k小的满足(2^L)|(P-1)且P为质数的P.Y ...

  2. .h 和 .cpp 切换快捷键设置(转)

    原文转自 https://blog.csdn.net/t163361/article/details/51859274 打开VS后依次选择如下 工具-> 选项-> 环境-> 键盘-& ...

  3. opengl glEnableClientState() 和 glDisableClientState() 作用

    http://zhidao.baidu.com/link?url=c3m55lgpjhU1Rb7TEP-aTGQAX3-GrcBk5NaUC2UA1ZtQiCCtHJzB_KoG7pWvPEybfYv ...

  4. ECMAScript和JavaScript的关系

    JavaScript 是一种基于 ECMAScript 规范的脚本语言,并在此基础上进行了自己的封装.ECMAScript 不是一种编程语言,仅仅是一种脚本语言规范,由欧洲计算机协会制定和发布,任何基 ...

  5. VirtualBox虚拟机安装CentOS 7

    新建虚拟机 因为比较简单,所以对于VirtualBox就不做过多介绍了,直接下载安装即可,安装好之后打开Oracle VM VirtualBox管理器,点击新建,选择Red Hat(根据windows ...

  6. Ionic 存储目录 CORS

    使用不同的存储库结构官方为 { "scripts": { "install": "cd path-to/your-app && npm ...

  7. Python的并发并行[1] -> 线程[2] -> 锁与信号量

    锁与信号量 目录 添加线程锁 锁的本质 互斥锁与可重入锁 死锁的产生 锁的上下文管理 信号量与有界信号量 1 添加线程锁 由于多线程对资源的抢占顺序不同,可能会产生冲突,通过添加线程锁来对共有资源进行 ...

  8. [POI2006]Periods of Words

    题目大意: 给定一个长度为$n(n\leq10^6)$的字符串$S$,定义一个串$S$的最大周期为一个不为$S$的字符串$Q$,满足$Q$为$S$的前缀且$S$为$QQ$的前缀.求字符串$S$的每一个 ...

  9. 1.12(java学习笔记)多态及向上、向下转型

    一.多态 多态是指同一个方法被调用,由于对象不同导致行为不同. 例如调用自由活动方法,张三喜欢玩耍,那么他就会去玩耍. 李四喜欢学习,那么他可能去学习.调用方法因对象的不同 而产生了不同的行为. 形成 ...

  10. DQL数据查询语言——连接查询

    --内连接 两种写法 等值连接select r.*,b.bummc from t_hq_ryxx r, t_hq_bm b where r.bumbm = b.bumbm select r.*,b.b ...