博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
左值 右值 拷贝构造 移动构造
阅读量:4110 次
发布时间:2019-05-25

本文共 2630 字,大约阅读时间需要 8 分钟。

左值 右值 拷贝构造 移动构造

读此篇文章,你得先了解变量在寄存器中的赋值过程和生命周期。

左值

左值的意思很简单,就是左边的值,在编程语言中,左边的值一般来说是变量

int var = 10;

此时 var 就是左值。准确的来说左值就是可以被赋值的值,也就是变量。

右值

还是上面那段代码,在右边的就是右值,所以10就是右值,这么解释其实并不完全对。因为可能有些人会认为 这种情况也是右值:

int var = 10;	int param = var;

有些人会理解成var 此时变成了右值。实际上右值很好理解,可以理解为常量,内存中实际的值。简单的来说,右值是不能被赋值的,比如 10 不能写成 10 = 11 。

说到右值,就得说右值引用,那么可以用来做什么? 看下面这个例子

int GetInt(){
return 10;} int&& var = GetInt(); return 0;

如果没有用右值引用,那么GetInt会返回int后会出现一个临时变量,临时变量用来给var赋值。但是此时使用了右值引用,那么var使用的其实就是GetInt中的10,会延长临时变量的生命周期。

拷贝构造

我们都知道,在clone的时候,如果类中有指针成员,那么在做拷贝的时候通常会进行深拷贝,像这种:

class Parent {
private: Son* son;public: ~Parent() {
delete son; } Parent() :son(new Son()) {
}; Parent(const Parent& parent) :son(new Son(parent.son)) {
}; //深拷贝};

这么做没有什么不对,但是此时,你不想让用户了解这个类具体的构造过程,所以你写了个Factory,像这样:

Parent GetParent(){
return Parent(); //返回一个右值} //用户使用Parent p = GetParent();

这种情况下,资源会被浪费到什么地步?关闭编译器优化我们理一下过程

  1. GetParent中会构造一次Parent
  2. GetParent在返回时会进行析构,此时还没有析构
  3. 因为析构会导致无法赋值,所以在GetParent()会产生一个临时对象,所以又调用了一次拷贝构造
  4. GetParent返回析构。
  5. Parent p = 这里又调用一次对临时对象的拷贝构造
  6. 临时对象析构.
    总的来说 进行了1次构造,2次深度拷贝,2次析构,这里更不用说深拷贝中son的拷贝同样是2次,如果Son的资源占用很大,那么这种浪费是很可怕的。

但是我就想用Factory,不想让用户知道这个类具体的构造过程,怎么办?了解一下移动构造。

移动构造

在上面深拷贝造成的资源浪费后,我们想到了一种办法,使用右值引用来调用GetParent;

Parent&& p = GetParent();

现在的过程就会是这样:

  1. GetParent 中触发构造
  2. 由于延长了右值生命周期,不会出现临时对象,Parent&& p = 只会触发一次拷贝构造
  3. GetParent 中的对象析构

现在只会触发一次构造、一次深拷贝、一次析构。这里要说一下常量左值这个通用引用类型。,universal references 既可以接受左值也可以接收右值,const Parent& p = GetParent(); 是相同的效果。

还有没有办法优化呢?

因为我们使用右值引用后,GetParent中的对象被延长生命周期,可以让我们只进行一次深拷贝

但是GetParent中的对象,析构还是会触发son的delete,事实上GetParent中的对象我们根本用不上,延长生命周期也只是为了减少深拷贝次数,能不能省去它成员(son)的delete和深拷贝中的new呢?

于是 我们优化一下Parent这个类:

class Parent{
private: Son *son;public: ~Parent(){
if (son!=nullptr) delete son;} Parent() :son(new Son()) {
}; Parent(const Parent& parent) :son(new Son(parent.son)) {
}; //深拷贝 //如果是右值引用,我们直接将son指针赋值,不进行深拷贝,只是浅拷贝,并且将原来的son的指针置空 Parent(Parent&& parent):son(parent.son) {
parent.son = nullptr; } //或者这么写 //Parent(Parent&& parent) { std::swap(son, parent.son); }}

使用:

Parent p  = GetParent();

按照惯例,还是写一下过程:

  1. GetParent 触发构造,然后返回右值,因为析构后会无法赋值,所以出现一个临时变量
  2. 临时变量 会触发一次移动构造
  3. GetParent 中的对象析构
  4. Parent p = 又触发一次移动构造
  5. 临时对象析构
    总的来说 一次构造,2次移动构造,2次析构

如果想更节约 可以 const Parent& p = GetParent();

移动构造会让我们省去成员中指针的堆创建和释放,极大的提高了性能。

事实上目前的编译器都会进行优化,所以上面的例子其实只会进行一次构造,连拷贝构造都用不上

写这个原因是让大家清楚右值引用的作用,既然编译器会优化那么就毫无用处了吗?并不是,可以这么用,下面的例子在编译器优化后是这样的:

Parent temp = GetParent(); //只进行一次构造函数	//todo something	//move其实是将左值转为右值,move只是表明语义,进行移动构造。	Parent p = std::move(temp); //移动构造

关于泛型说明

如果是T&这既可以是左值也可以是右值,因为这个T不确定的,const Class& (universal references)也同样既可以是左值也可以是右值,因为const语义本身就是不可变,一个不可变的左值也可以是右值

转载地址:http://ixlsi.baihongyu.com/

你可能感兴趣的文章
Objective-C 基础入门(一)
查看>>
Flutter Boost的router管理
查看>>
iOS开发支付集成之微信支付
查看>>
C++模板
查看>>
【C#】如何实现一个迭代器
查看>>
【C#】利用Conditional属性完成编译忽略
查看>>
DirectX11 光照演示示例Demo
查看>>
VUe+webpack构建单页router应用(一)
查看>>
Node.js-模块和包
查看>>
(python版)《剑指Offer》JZ01:二维数组中的查找
查看>>
管理用户状态——Cookie与Session
查看>>
Spring MVC中使用Thymeleaf模板引擎
查看>>
PHP 7 的五大新特性
查看>>
深入了解php底层机制
查看>>
PHP中的stdClass 【转】
查看>>
XHProf-php轻量级的性能分析工具
查看>>
OpenCV gpu模块样例注释:video_reader.cpp
查看>>
【增强学习在无人驾驶中的应用】
查看>>
OpenCV meanshift目标跟踪总结
查看>>
就在昨天,全球 42 亿 IPv4 地址宣告耗尽!
查看>>