C++的移动构造和移动赋值运算符

2023-09-15 09:03:26

右值引用

右值引用(rvalue references)是一种新的用于绑定右值的引用类型。

那么什么是右值?

右值通常是编译器生成的用于表达式计算的临时变量或常量。目前来说,我们还不能安全地使用引用变量来绑定右值。

从编译原理上讲,右值是只存在于表达式计算时的未命名值

下面这一表达式产生了一个右值:

x+(y*z); // A C++ expression that produces a temporary

对于上面的表达式,C++编译器会生成一个临时变量(右值)来存储y*z的结果,然后将其加上x。理论上,这个生成的临时变量(右值)会在整个表达式处理完成后被抛弃。

下面是使用C++定义右值引用的一个例子:

std::string&& rrstr; //C++11 rvalue reference variable

之前C++的引用变量:

std::string& ref;

现在有了新的名字,叫做左值引用。

现在,右值引用在C++中无处不在,并深刻影响着变量的生命周期。想要理解这一影响,就需要了解C++的移动语义(move semantics)。

移动语义(move semantics)

首先,我们定义一个对象的状态是其非静态成员变量的值的集合。

了解移动语义(move semantics)之前,我们需要先了解复制语义(copy semantics)。复制语义(copy semantics)会在不修改源对象的情况下,将目标对象的状态设置为和源对象一样。比如,复制字符串变量s1到s2的结果是两个完全独立的拥有相同状态的字符串。

然而,现实中的很多场景,我们并不是在复制对象,而是在移动对象。比如,当我们付房租时,房东会将钱从我们的银行账户取走,这就是在进行对象的移动。还有,我们从手机中取出SIM卡,然后将SIM卡安装在一部新的手机中,这也是在移动。我们经常使用的电脑上的复制粘贴功能实际上也是移动操作。

复制和移动不仅仅是概念上的不同,在实践上,移动操作通常要比复制操作快得多:移动操作只是将一个已经创建好得资源移动到新的位置,而复制操作则会创建一个新的资源,然后复制旧资源的状态。对于返回值为值类型的函数来说,移动操作的提升尤为明显:

string func()

{

string s;

//do something with s

return s;

}

string mystr=func();

对于上面的代码,在func()函数返回时,C++编译器会在调用它函数的栈内存上生成一个临时对象复制对象s,接着对象s被销毁掉,生成的临时对象被作为参数复制构造mystr对象,构造mystr对象完成后,临时对象也被销毁掉。可以看出,整个过程包含了不少复制和销毁操作,如果使用移动操作,可以减少这类不必要的复制和销毁操作。

移动一个字符串对象几乎是没有代价的,只需要将源字符串对象的数据成员变量的值赋给目标字符串对象的数据成员变量即可。而复制一个字符串对象需要动态分配内存,然后从源字符串对象复制每一个字符。

用于移动操作的特殊成员函数

C++11引入了两个新的特殊成员函数用于移动操作:移动构造(move constructor)移动赋值运算符(move assignment operator)函数。它们是对下面这4个特殊成员函数的补充:

  • 默认构造函数(default constructor)
  • 复制构造函数(copy constructor)
  • 复制赋值运算符函数(copy assignment operator)
  • 析构函数(destructor)

如果一个类(class)不包含任何用户定义的特殊成员函数(排除默认构造函数),C++会隐式地定义它们。

class S{};

对于上面的代码,它没有包含任何用户定义的特殊成员函数,C++会隐式地为其定义特殊成员函数。隐式定义的移动构造会使用成员变量的移动构造函数来移动成员变量,隐式定义的移动赋值运算符会使用成员变量的移动赋值运算符来移动成员变量。

一个对象被移动后,它的状态是未定义的,我们可以认为一个被移动过的对象不再拥有任何资源,不严格地,我们可以认为被移动过地对象的状态为空集。比如,我们将字符串s1移动到字符串s2,移动后,s2的状态和移动前的s1状态相同,s1则变成了一个空字符串。

使用移动构造函数

C::C(C&& other); //C++11 move constructor

上面的代码演示了如何定义一个移动构造成员函数。移动构造成员函数不会分配任何新的资源,它会将other的资源占用,然后将other的状态设置为默认构造后的状态。

下面,我们使用一个具体的例子来解释这一过程。我们定义一个MemoryPage类来表示缓冲内存:

class MemoryPage

{

size_t size;

char * buf;

public:

explicit MemoryPage(int sz=512):

size(sz), buf(new char [size]) {}

~MemoryPage( delete[] buf;}

//typical C++03 copy ctor and assignment operator

MemoryPage(const MemoryPage&);

MemoryPage& operator=(const MemoryPage&);

};

一个典型的移动构造函数定义如下:

//C++11

MemoryPage(MemoryPage&& other): size(0), buf(nullptr)

{

// pilfer other's resource

size=other.size;

buf=other.buf;

// reset other

other.size=0;

other.buf=nullptr;

}

可以看出,移动构造函数既不分配内存,也不进行内存缓冲的复制,当然也就快了很多。

使用移动赋值运算符函数

C& C::operator=(C&& other);//C++11 move assignment operator

移动赋值运算符和复制构造函数类似,但是在占用源对象资源之前,会先释放自身的资源。移动复制运算符函数的执行逻辑如下:

  • 释放当前*this的资源
  • 占用other的资源
  • 设置other为默认构造状态
  • 返回*this

下面的代码演示了上述执行逻辑:

//C++11

MemoryPage& MemoryPage::operator=(MemoryPage&& other)

{

if (this!=&other)

{

// release the current object's resources

delete[] buf;

size=0;

// pilfer other's resource

size=other.size;

buf=other.buf;

// reset other

other.size=0;

other.buf=nullptr;

}

return *this;

}

函数重载

为了支持右值引用,C++ 11修改了函数重载的规则。比如像vector::push_back()这类标准库的函数现在都有两个重载版本,一个使用const T&作为参数用于之前的左值引用,一个新的T&&用于右值引用。

#include <vector>

using namespace std;

int main()

{

vector<MemoryPage> vm;

vm.push_back(MemoryPage(1024));

vm.push_back(MemoryPage(2048));

}

因为参数是右值,上面代码的两次push_back()调用实际调用的都是push_back(T&&)。push_back(T&&)函数将资源从参数给出的MemoryPage对象使用MemoryPage的移动到vector内部的MemoryPage对象。对于之前版本的C++,因为调用的是复制构造函数,则会发生额外的内存分配与复制。

之前提到,当参数是左值时,会调用push_back(const T&)函数:

#include <vector>

using namespace std;

int main()

{

vector<MemoryPage> vm;

MemoryPage mp1(1024);//lvalue

vm.push_back(mp); //push_back(const T&)

}

但我们仍然可以使用static_cast强制将一个左值转换为右值:

//calls push_back(T&&)

vm.push_back(static_cast<MemoryPage&&>(mp));

也可以用std::move这一新的标准库函数来完成转换:

vm.push_back(std::move(mp));//calls push_back(T&&)

看上去,在大多数情况下,我们需要的是push_back(T&&)函数,但需要记住的是push_back(T&&)会将源对象的状态设置为空,如果我们想让源对象的状态保持不变,应该使用复制构造语义。一般而言,我们应该同时定义移动构造函数,移动赋值运算符函数,复制构造函数,复制赋值运算符函数。

链接:https://zhuanlan.zhihu.com/p/222984499

总结

使用移动构造函数和移动赋值运算符函数只需要少量修改就能使旧代码获得巨大的性能提升,何乐而不为呢?

更多推荐

ddos打到高防cdn上会发生什么

ddos打到cdn上会发生什么?当DDoS攻击打到CDN上时,肯定会影响网站的可用性和用户体验。具体DDoS攻击打到CDN上时,会发生以下情况:CDN节点负载增加:DDoS攻击会导致大量的无效流量涌入CDN节点,从而使得节点负载增加。这可能会导致节点响应变慢,甚至出现崩溃、停止响应等情况。影响用户访问:如果DDoS攻击

ArcGIS Engine:C#基础语法的了解

目录01前言02实验2.1创建“HelloWorld”程序2.2创建程序,完成成绩评定功能2.3创建程序,完成1到100的累加2.4窗体应用程序01前言学了一些语言了,确实发现,语言只是工具,只有你作为初恋的语言值得你花大量时间去深究。而对于其他作为应用或者说只是作为桥梁的语言去学习其他内容的课程,没有必要过分深究语言

Spring Task

Spring框架提供了一套任务调度的功能,可以帮助开发者实现定时任务和异步任务的管理和调度。任务调度是指按照预定的时间表执行某个任务或一系列任务,Spring的任务调度模块可以在应用程序中方便地配置和管理这些任务。Spring框架中任务调度的核心是通过TaskScheduler接口和@Scheduled注解来定义和执行

OpenHarmony ArkTS工程目录结构(Stage模型)

一、应用工程结构图片来源:OpenHarmony官网AppScope>app.json5:应用的全局配置信息。entry:OpenHarmony工程模块,编译构建生成一个HAP包。src>main>ets:用于存放ArkTS源码。src>main>ets>entryability:应用/服务的入口。src>main>e

Java拦截器与过滤器的区别

主要结论:运行顺序不同,过滤器先,拦截器后配置方式不同,过滤器web.xml,拦截器spring的配置文件过滤器依赖于servlet,拦截器依赖于Spring过滤器只能对request和response响应,拦截器还能对springmvc生态下的组件做处理。(说人话就是咱们现在用的都是人家spring的产品,那么拦截器

golang入门笔记——pprof性能分析

文章目录简介runtime/pprof的使用命令行交互网络服务性能分析pprof与性能测试结合压测工具go-wrk简介golang性能分析工具pprof的8个指标1.性能分析的5个方面:CPU、内存、I/O、goroutine(协程使用情况和泄漏检查)、死锁检测以及数据竟态分析runtime.SetMutexProfi

天地图绘制区域图层

背景:业务方要求将原效果图参考效果图最终实现效果变更点:1.将原有的高德地图改为天地图2.呈现形式修改:加两层遮罩:半透明遮罩层mask+区域覆盖物mask实现过程:1.更换地图引入源<linkrel="stylesheet"href="https://cdn.jsdelivr.net/npm/maptalks/dis

IntelliJ IDEA使用——常规设置

文章目录版本说明主题设置取消检查更新依赖自动导入禁止importxxx.*、允许import内部类显示行号、方法分割线、空格代码提示(匹配所有字母)自定义注释颜色添加头部注释自定义字体设置字符编码关联本地GitJDK编译版本Maven配置Tomcat配置代码注释设置头部注释单行注释HTML和XML注释IDEA同步设置版

使用电力系统稳定器 (PSS) 和静态 VAR 补偿器 (SVC) 提高瞬态稳定性(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。⛳️座右铭:行百里者,半于九十。📋📋📋本文目录如下:🎁🎁🎁目录💥1概述📚2运行结果🎉3参考文献🌈4Simulink仿真实现💥1概述电力系统稳定器(PSS)和静态VAR补偿器(S

tomcat敏感数据加密实现方案

1背景tomcat部署的SSM老项目,在tomcat的context.xml下配置了数据源信息,且部分敏感信息都是明文,这是一项严重的不安全因素。故需要将数据库密码这种敏感信息进行加密。2实现方案2.1继承重写工厂方法这种方法需要在原应用工程中添加扩展工厂类,用于处理tomcat配置文件中敏感信息。优点是不被tomca

VR全景拍摄:打破传统拍摄角度限制,营造全新体验

VR全景拍摄不仅仅是拍摄环境,更多的是展示意境,我们的传统文化就是讲究意境,仅仅是看一张清晰无比的图片,自然显得没有趣味,但是这种真实的视觉体验,明明不在现场却能直观体验现场场景,这种意境可以让人们更加深入地了解事物的本质。随着VR技术的普及,越来越多的人开始使用VR全景拍摄来展示自己的店铺,VR全景拍摄具备很好的视觉

热文推荐