对于c++11来说移动语义是一个重要的概念,一直以来我对这个概念都似懂非懂。最近翻翻资料感觉突然开窍,因此记下。其实搞懂之后就会发现这个概念很简单,并无什么高深的地方。

先说说右值引用。右值一般指的是表示式中的临时变量,在c++中临时变量在表达式结束后就被销毁了,之后程序就无法再引用这个变量了。但是c++11提供了一个方法,让我们可以引用这个临时变量。这个方法就是所谓的右值引用。

那么右值引用有什么用呢?避免内存copy!

不同于其它语言,在c++里变量是值语义(在JAVA、Python变量是引用语义)。因此对于赋值操作意味着内存拷贝而不是简单的赋值指针。而右值引用的一个作用就是我们可以通过重新利用临时变量(右值)来避免无意义的内存copy。

看这个例子(取自Rvalue References: C++0x Features in VC10, Part 2

 string s0(“my mother told me that”);
string s1(“cute”);
string s2(“fluffy”);
string s3(“kittens”);
string s4(“are an essential part of a healthy diet”); string dest = s0 + ” ” + s1 + ” ” + s2 + ” ” + s3 + ” ” + s4;

第7行对string求和中,每次调用operator+()都会创建一个临时变量,一共创建了8个临时的string对象。而这里真正低效的原因是,每次创建一个string对象都需要从堆上申请空间,将字符串的内容copy进来,并且这些临时的string都是只用一次。

显然这是低效的,为什么不这样子做呢:假设s0+""的时候创建的临时变量是temp_str,此后我们一直是用这个临时变量而不是重写创建。这样做表达式的结果是不会有任何改变,并且因为在opertor+()创建的都是程序其它地方不会引用到的临时变量,所以这样做也不会有任何副作用。相当于我们偷偷的做了个其它人无法察觉的小优化。

要如何做才能引用这个临时的string对象呢?答案就是右值引用。通过右值引用我们就能重新利用表达式中的临时变量,并且以更高效的指针swap来避免内存copy。

需要说明的一点是,右值引用优化的是避免对象在堆空间的内存的copy。在堆上的内存我们可以简单的通过指针交换来传递内存资源的所有权(类似于vector的swap方法),而对于栈上的内存不可避免还是需要copy。举一个例子这里移动对象有点像放风筝:我放风筝,放着放着觉得累了就交给你,具体是怎么交法呢?你先制作一个和我手上拿的一模一样的手柄,接着我再把线剪短递给你,你把线绑在你的手柄上,交接完毕!手柄对应是栈空间、风筝对应是堆空间。

这种技术十分有用,不仅仅是在处理临时变量的时候起作用,有的时候我们想要使用这个转移资源(内存)的效果时,也可以强制将类型转为右值引用(std::move)来触发对象移动。

举一个例子,比如说对于vector的动态扩容。熟悉vector的实现的都知道,在对一个vector进行push_back时有可能会触发内存的重新分配,这个时候需要把原来内存的对象copy到新分配的内存上,最后再释放原来的内存。假设这个vector里面存放的是string对象,那么我们在执行简单的对象赋值(调用的是string::operator=()方法)的过程中,我们copy的不仅仅是sizeof(string)的内存,我们还copy这个string内部指针指向堆空间上的内存。通过观察可以发现,其实我们完全不必去拷贝内部指针指的那部分内存,因为原来的string对象在赋值完后就要被销毁,如果我们将这个指针偷偷的拿过来(swap),程序的其它部分不会有任何察觉。为了实现这样的操作我们需要做以下两件事情:

  1. 对string实现一个移动构造函数、移动赋值函数。这些函数对内部指针进行swap操作,而不是copy操作。
  2. 通过std::move来强化转化成右值引用,用以触发移动赋值函数。编译器正是通过参数类型是T&&,才知道应该使用移动版本的operator=()而不是copy版本的operator=()。
 new_stri = std::move(old_str);

要对old_str转化为右值引用是因为它并不是真正的右值,它不是一个临时变量。但因为它即将被销毁,所以效果等同于一个临时变量。因此可以安全的转换,从而调用移动赋值函数并悄悄的"移动"它的内存资源。

推荐阅读:

  1. 知乎:如何理解C++中的move语义?邱昊宇的回答

参考资料:

  1. Visual C++ Team Blog:Rvalue References: C++0x Features in VC10, Part 2

c++11的右值引用、移动语义的更多相关文章

  1. C++11之右值引用(二):右值引用与移动语义

    上节我们提出了右值引用,可以用来区分右值,那么这有什么用处?   问题来源   我们先看一个C++中被人诟病已久的问题: 我把某文件的内容读取到vector中,用函数如何封装? 大部分人的做法是: v ...

  2. c++11之右值引用

    本文大部分来自这里,并不是完全着行翻译,如有不明白的地方请参考原文. 在c++中,创建临时对象的开销对程序的影响一直很大,比如以下这个例子: String getName(){ return “Kia ...

  3. C++11 的右值引用

    作者:Tinro链接:https://www.zhihu.com/question/22111546/answer/30801982来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  4. C++11之右值引用(一):从左值右值到右值引用

    C++98中规定了左值和右值的概念,但是一般程序员不需要理解的过于深入,因为对于C++98,左值和右值的划分一般用处不大,但是到了C++11,它的重要性开始显现出来. C++98标准明确规定: 左值是 ...

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

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

  6. C++ 11的右值引用

    目录 一.问题导入 二.右值和右值引用 2.1 左值(lvalue)和右值(rvalue) 2.2 左值引用和右值引用 总结 参考资料 C++11 引入了 std::move 语义.右值引用.移动构造 ...

  7. C++11特性-右值引用

    什么是左值,什么是右值 常见的误区有 = 左边的是左值,右边的是右值. 左值:具有存储性质的对象,即lvalue对象,是指要实际占用内存空间.有内存地址的那些实体对象,例如:变量(variables) ...

  8. [转载]如何在C++03中模拟C++11的右值引用std::move特性

    本文摘自: http://adamcavendish.is-programmer.com/posts/38190.htm 引言 众所周知,C++11 的新特性中有一个非常重要的特性,那就是 rvalu ...

  9. C++11之右值引用(三):使用C++11编写string类以及“异常安全”的=运算符

    前面两节,说明了右值引用和它的作用.下面通过一个string类的编写,来说明右值引用的使用. 相对于C++98,主要是多了移动构造函数和移动赋值运算符. 先给出一个简要的声明: class Strin ...

随机推荐

  1. s5pv210编译qt

    undefined reference to `rpl_malloc' 编译tslib,执行make时提示undefined reference to `rpl_malloc' 是因为config.h ...

  2. Hdu 4681 2013 Multi-University Training Contest 8 String

    带跨越式的LCS,同样是在朴素的LCS上加入一种跨越一段的转移,这样我们要预处理出跨越一段给定串的转移函数. 这个题同样可以正反两边LCS做 呆马: #include <iostream> ...

  3. STM32的USART中断死循环,形成死机。

    作者:观海  QQ:531622 直接说重点:我用的是 STM32F103 芯片 USART2_IRQHandler 总是中断,程序死循环. 1.出现问题: 原程序的中断处理程序是: void USA ...

  4. 如何结合自己本地数据库,使用【百度地图】API

    如何结合自己本地数据库,使用[百度地图]API百度地图使用越来越多,官网上的示例数据都是写死的,实际上我们的开发中的数据都是从数据库中取出来的,最近看了很多大神的文章,结合自己本地数据库使用百度地图A ...

  5. Linux的常用命令

    1.cd命令 这是一个非常基本,也是大家经常需要使用的命令,它用于切换当前目录,它的参数是要切换到的目录的路径,可以是绝对路径,也可以是相对路径.如:   cd /root/Docements # 切 ...

  6. springmvc json字符串转化成json对象

    问题出现在 :页面数据列表的展示出现 [object HTMLInputElement] 找到问题的所在原因后又三种解决方案 一:格式化json字符串为json对象字符串 success:functi ...

  7. NOI 动态规划题集

    noi 1996 登山 noi 8780 拦截导弹 noi 4977 怪盗基德的滑翔翼 noi 6045 开餐馆 noi 2718 移动路线 noi 2728 摘花生 noi 2985 数字组合 no ...

  8. 连接MySQL错误:Can't connect to MySQL server (10060)

    问题原因: 导致些问题可能有以下几个原因: 1.网络不通: 2.服务未启动: 3.防火墙端口未开放: ----防火墙端口未开放的可能性比较大

  9. ECNAScript6简介

    ECMAScript6.0(以下简称ES6)是JavaScript语言的下一代标砖,已经在2015年6月正式发布了,它的目标,是使得JavaScript语言可以用来编写负责的大型应用程序 ,成为企业级 ...

  10. getFragmentManager()和getSupportFragmentManager()

    在Android开发中,少不了Fragment的运用.目前在实际运用中,有v-4包下支持的Fragment以及app包下的Fragment,这两个包下的FragmentManager获取方式有点区别, ...