理解右值引用

  • 前言
  • 问题复现
  • 自定义string(CMyString)
  • 遇到问题
  • 图示理解
  • 右值引用
    • 什么是右值
    • 添加右值引用参数的成员方法
  • 结果对比
  • 解决遗留问题

前言

在之前,我写过一篇:
通过自定义string类型来理解运算符重载。
在文章末尾,我提出了一个问题:
那么如何能够实现不泄露内存,并且提高效率呢?
那么,今天就来看看如何解决这个问题。

问题复现

自定义string(CMyString)

首先是对string类型的一个简单实现,代码如下:

class CMyString
{public:CMyString(const char* str = nullptr){cout << "CMyString(const char*)" << endl;if (str != nullptr){_pstr = new char[strlen(str) + 1];strcpy(_pstr, str);}else{_pstr = new char[1];*_pstr = '\0';}}~CMyString(){cout << "~CMyString" << endl;delete[] _pstr;_pstr = nullptr;}CMyString(const CMyString& str){cout << "CMyString(const CMyString&)" << endl;_pstr = new char[strlen(str._pstr) + 1];strcpy(_pstr, str._pstr);}CMyString& operator=(const CMyString& src){cout << "operator=(const CMyString&)" << endl;if (this == &src)return *this;delete[]_pstr;_pstr = new char[strlen(src._pstr) + 1];strcpy(_pstr, src._pstr);return *this;}const char* c_str()const { return _pstr; }
private:char* _pstr;
};
};

注意:
vs2017vs2019这样版本较新的编译器,使用strcpy()函数一般都会报错:

对于vs2017来说,可以直接在项目->属性

C/C++ -> 常规 -> SDL检查 -> 改为否

之后点击应用 -> 确定就好了。
但是对于vs2019来说,上述方法并不起作用:
博主经过实践,发现可以用如下方法解决:
在文件开始添加宏定义:
#pragma warning(disable:4996)

这样,就可以使用strcpy()了!

遇到问题

之前的博客提到过,对于正常的逻辑实现,这个代码是没有问题的,但是涉及到对象的优化
(可以移步到我的这篇博客:C++对象优化)
或者换句话说,涉及到提高代码的效率,就会出现问题:
同样地,我们使用如下代码:

CMyString GetString(CMyString& str)
{const char* pstr = str.c_str();CMyString tmpstr(pstr);return tmpstr;
}int main()
{CMyString str1("aaaaaaaaaaaaaaaaaaaaaaaaa");CMyString str2;str2 = GetString(str1);cout << str2.c_str() << endl;return 0;
}

运行结果如下:

图示理解

可能直接从代码运行结果来看并不太直观,我们来画图分析:

解读:
首先第一步、第二步很好理解,都是调用了构造函数进行对象的构造;
接着实参到形参因为使用引用传递,所以不会产生新对象;
进入GetString()函数,首先构造局部对象tmpstr
接着就执行return语句,由于局部对象不能带出局部作用域,所以为了能够带出局部对象的值,需要在主函数栈帧上构建一个临时对象,从局部对象到临时对象调用了拷贝构造函数
关键点:
这个时候我们的代码的逻辑是:
只要执行拷贝构造函数,就会依据原对象的尺寸大小来开辟一个新的空间,然后将原来的数据依次拷贝(strcpy)进新的对象空间
这样的逻辑本身没有什么问题,但是一般自定义的类型都会在堆上开辟一定的空间,并且,这个空间中的数据可能很多。
执行一次拷贝构造,可能需要大量的内存开辟数据复制
最关键的,规模如此庞大的内存开辟数据复制完成后,原来的对象就析构了!
那你早说啊!你把你的资源直接给我不就好了?
浪费这么多感情和精力干嘛?

同理,在主函数的栈帧上构建的临时对象给str2进行赋值的时候,也同样需要大量的内存开辟数据复制,拷贝完成后,临时对象也就析构掉了。

所以,对于这种涉及到临时对象的构造和赋值时,我们不能使用常规的逻辑:
将我的资源复制一份给你
而应该转换逻辑:
将我的资源转移给你(因为反正我也不用了即将析构掉
这样的话,代码的效率会有很大的提升!

右值引用

那么如何解决上述的问题呢?
其实通过分析我们已经知道,问题就出在临时对象上面。因为拿正常的逻辑去对临时对象操作,会有大量资源的消耗。
为了解决这个问题,我们需要区分普通对象临时对象
那么体现在自定义类型中就是左值引用右值引用的区别了。
我们可以首先来看看什么是右值。

什么是右值

要理解右值,可以从左值开始。
百度百科对于左值和右值的概念为:

左值(lvalue)和右值(rvalue)最先来源于编译。在C语言中表示位于赋值运算符两侧的两个值,左边的就叫左值,右边的就叫右值。
左值:指的是如果一个表达式可以引用到某一个对象,并且这个对象是一块内存空间且可以被检查和存储,那么这个表达式就可以作为一个左值。
右值:指的是引用了一个存储在某个内存地址里的“数据”。

从上数定义我们可以简单概括为:
左值:有内存、有名字;
右值:没内存或者没名字(临时量)。

添加右值引用参数的成员方法

所以我们上面说的临时对象就是一个右值
需要注意的一点是:
右值引用变量本身也是一个左值

对于这句话的理解是:
一个右值(临时量),是没有名字的;
一个引用,就是相当于给变量起了别名
右值和引用一结合,就有了名字、有了内存;
那么也就变为了左值

这一点特别重要:在后面moveforward应用的时候需要特别注意!

接下来我们就可以在原来CMyString类型里添加带有右值引用参数的成员方法(主要是拷贝构造赋值重载):

// 带右值引用参数的拷贝构造
CMyString(CMyString &&str) // str引用的就是一个临时对象
{cout << "CMyString(CMyString&&)" << endl;_pstr= str._pstr;str._pstr= nullptr;
}// 带右值引用参数的赋值重载函数
CMyString& operator=(CMyString &&str) // 临时对象
{cout << "operator=(CMyString&&)" << endl;if (this == &str)return *this;delete[]_pstr;_pstr= str._pstr;str._pstr = nullptr;return *this;
}

结果对比

修改代码之后再次运行,结果如下:

我们可以看到,这一次,匹配的都是右值引用参数的成员方法了!

解决遗留问题

了解了右值引用之后,我们发现,右值引用对于解决涉及临时对象大量内存开辟及数据拷贝的问题有着很好的应用。
所以,我们之前遗留的问题:
CMyString+运算符重载函数就可以解决了:

CMyString operator+(const CMyString &lhs,const CMyString &rhs)
{CMyString tmpStr;tmpStr.mptr = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];strcpy(tmpStr.mptr, lhs.mptr);strcat(tmpStr.mptr, rhs.mptr);return tmpStr;
}

注意:
在类外定义+运算符重载函数的时候,需要在类里面定义一个友元函数

class CMyString
{private:friend CMyString operator+(const CMyString &lhs,const CMyString &rhs);
};

这样的话,在涉及临时对象拷贝构造赋值重载,都将匹配到带有右值引用参数的成员方法。
这样就可以避免大量开辟内存数据拷贝了!
代码的效率得到了极大的提高!

从自定义string类型理解右值引用相关推荐

  1. 看完这个你还不理解右值引用和移动构造 你就可以来咬我(下)

    共分三篇,这是第3篇.另外两篇,看完这个你还不理解右值引用和移动构造 你就可以来咬我(上),看完这个你还不理解右值引用和移动构造 你就可以来咬我(中). 下面是新标准下的其他特性(共十点),了解一下, ...

  2. 看完这个你还不理解右值引用和移动构造 你就可以来咬我(中)

    共分三篇,这是第2篇.另外两篇,看完这个你还不理解右值引用和移动构造 你就可以来咬我(上),看完这个你还不理解右值引用和移动构造 你就可以来咬我(下). 看完上篇,应该对右值引用和移动构造有了总体上的 ...

  3. 看完这个你还不理解右值引用和移动构造 你就可以来咬我(上)

    共分三篇,这是第一篇.另外两篇,看完这个你还不理解右值引用和移动构造 你就可以来咬我(中),看完这个你还不理解右值引用和移动构造 你就可以来咬我(下). C++ 右值引用 & 新特性 C++ ...

  4. 深入理解右值引用,move语义和完美转发

    move语义 最原始的左值和右值定义可以追溯到C语言时代,左值是可以出现在赋值符的左边和右边,然而右值只能出现在赋值符的右边.在C 里,这种方法作为初步判断左值或右值还是可以的,但不只是那么准确了.你 ...

  5. C++11新特性(一)右值引用

    @ 一.C++11简介 在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称.不过由于TC1主要是对C+ ...

  6. 【C++】C++11 右值引用和移动语义

    文章目录 一.左值与左值引用 二.右值与右值引用 三.左值引用和右值引用的比较 四.右值引用的使用场景和意义 1.左值引用的短板 2.移动构造和移动赋值 3.STL 容器的变化 五.万能引用与完美转发 ...

  7. Google C++ Coding Style:右值引用(Rvalue Reference)

    右值引用是一个C++11特性,标记为T&&.GSG中定义:只为移动建构函数(Move constructor)和移动赋值操作(Move assignment)使用右值引用.并且不要使用 ...

  8. C++11中的右值引用及move语义编程

    C++0x中加入了右值引用,和move函数.右值引用出现之前我们只能用const引用来关联临时对象(右值)(造孽的VS可以用非const引用关联临时对象,请忽略VS),所以我们不能修临时对象的内容,右 ...

  9. C++ 11右值引用

    C++ 11中引入的一个非常重要的概念就是右值引用.理解右值引用是学习"移动语义"(move semantics)的基础.而要理解右值引用,就必须先区分左值与右值.        ...

最新文章

  1. 澳门大学燕茹教授课题组招聘/招生启事
  2. JAVA-微信支付开发
  3. 【java基础】map的基本使用与字符串中每个字符出现的次数统计
  4. PAT甲级1056 Mice and Rice:[C++题解]模拟、排名
  5. 马云:我看到很多人去学MBA 但回来时都变蠢了
  6. (转载)VS2010/MFC编程入门之一(VS2010与MSDN安装过程图解)
  7. 敏捷开发般若敏捷系列之二:什么是敏捷(上)(无住,不住于法,破法执)...
  8. ChromeDriver和PhantomJS配置到$PATH
  9. 2022年认证杯SPSSPRO杯数学建模A题(第二阶段)人员的紧急疏散求解全过程文档及程序
  10. 手绘风格的原型图制作工具
  11. FreeSwitch基础知识
  12. 摄像头视频直播方案比较之方案一:萤石云
  13. 机器学习可解释性之shap模块的使用——基础用法(一)
  14. 面试篇1:嵌入式C语言面试常见问题
  15. 二分法和牛顿迭代法求平方根(Python实现)
  16. IBM Websphere MQ 使用详解
  17. cc2540 定频,设置发射功率
  18. 相机标定与矫正(总结)
  19. 【SemiDrive源码分析】系列文章链接汇总(全)
  20. LD3320和MP3模块搭建语音识别交互系统

热门文章

  1. (转) 何为机器学习
  2. 趋势信息整合(01) 谷歌google开发者 那些事儿
  3. 推荐3dMax三维设计十大插件
  4. GDAL坐标转换——TransformPoint
  5. 【OpenCV 4开发详解】图像模板匹配
  6. 志宇-Jenkins学习
  7. 通辽智慧城市建设总体规划和顶层设计专家评审会召开
  8. Bytebase数据库 Schema 变更管理工具
  9. iView UI --Table组件数据刷新但是页面未刷新
  10. STM32 ---deley延时两行代码实现【为方便移植文件】