C++——移动构造和完美转发

1.什么是右值

右值引用是C++11的概念,与之对应的是左值引用。

当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存当中的位置)。

以上的概念是摘录自《C++ primer》。

但是这样的概念并不足以理解。用一句简单的话描述左值和右值:左值都是可以被取地址的;右值都是不可以被取地址的。

根据结论可以快速判断出哪些是左值、哪些是右值:

int func() { return 100; }
int main()
{int x = 3;// 可以取地址,左值string s("hello");// 可以取地址、左值string("world");// 不可取地址、右值12;// 不可取地址、右值func();// 返回值不可取地址,右值return 0;
}

2.移动构造和移动赋值

左值引用只能引用左值,右值引用只能右值、const左值引用可以引用左值也可以引用右值。

单纯的右值引用没有意义。

右值引用的使用场景在于移动构造和移动赋值。

移动构造对应拷贝构造、移动赋值对应赋值运算符重载。

上面的概念是有问题的、不准确的。把他们放在一起的原因是要讨论"移动"和"拷贝"的区别。"移动"区别于"拷贝","移动"是不会发生拷贝的,它更像是一种"窃取"、"转移"。

也就是说,把右值的内容"转移"到其他地方去,从而减少不必要的拷贝。写一份伪代码和画一幅图来理解移"移动":

class String
{char *_str;
};int main()
{String s1(String("hello"));
}


 

 如上图,s1对象在调用构造的时候不会调用拷贝构造(如果实现了移动构造),s1当中的_str成员不会指向new出来的空间,而是指向匿名对象当中_str所指向的空间。

那么从上图看来会有一个潜在的问题,那就是有两个指针指向同一块空间,会有重复析构的风险。所以在编写移动构造的时候,要使资源被移动的对象能够正确析构(在这个例子中让_str指向空就行了)。

这里给出一段String类的移动构造吧:

String(String&& s)
{swap(s);s._str = nullptr;s._size = s._capacity = 0;cout << "String:移动构造" << endl;
}void swap(String& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);    
}

对于移动赋值来说也是一样的道理,都是对右值进行资源转移。这里给出String类的赋值运算符重载代码实现:

String &operator=(String s)
{swap(s);cout << "String:移动赋值" << endl;return *this;
}

这个写法是C++的现代写法,形参是一个对象并不是引用,所以在传参的时候会调用拷贝构造或者是移动构造(如果实参是右值的话),然后在移动赋值内部进行资源转移。

3.左值"转化"为右值

C++11提供了一个可以将左值"转化"为右值的接口,即std::move()。

实际上"转化"的说法是不对的,因为std::move()的返回值是一个实参的一个右值引用。

int main()
{String s1("hello");String s2("world");String s2 = std::move(s1);
}

在上面的代码当中,s2的赋值操作不会调用赋值重载,而是调用移动赋值。注意std::move()并不是将左值真实的转化为右值,而是返回左值的右值引用。

使用std::move()需要注意一件事情,就拿上面的代码来说,s1赋值给s2,移动赋值之后s1的指向不再指向"hello",而是指向"world"或者置空。

也就是说使用std::move()并且移动赋值给其他对象的对象,移动赋值结束之后最好不要使用它。

4.左值or右值?

有一个匪夷所思但是真实存在的一个问题,看下面的代码:

String(String&& s)
{swap(s);s._str = nullptr;s._size = s._capacity = 0;cout << "String:移动构造" << endl;
}

能够调用移动构造一定是实参是一个右值,那么拿匿名对象为例,它本身没有名字,也就无法直接使用它。但是它移动构造是一个右值引用,既然是引用那么它就是一个对象的别名。

所以得出一个结论,即右值传递给右值引用为参数的函数后,在该函数内部作为左值使用。

但是如果在这个函数内部就是想把它当做右值并且传递给其他函数来使用的话该怎么办?

5.完美转发

在C++11之前就有了万能引用的概念,C++11之后,万能引用的概念更加贴切了。

万能引用既可以引用左值、也可以引用右值。

它的写法就是一个模板类型的右值引用:

template <class T>
void func(T &&t)// 即可以引用左值、也可引用右值
{cout << t << endl;
}int main()
{func(15);// 传递右值string s1("nice");func(s1);// 传递左值return 0;
}

但是刚才说过,右值传递并且进入到func函数之后,它就变成了左值,那么如果在func函数内部又调用了一个函数,但是该函数的参数部分只接收右值引用,该怎么办?

void Print(int&& t)
{cout << t << endl;
}
template <class T>
void func(T &&t)// 即可以引用左值、也可引用右值
{Print(t);
}

如果像上面这么写就会喜提报错: 

 所以C++11提供了一个方法,即std::forword<T>()方法。它的作用就是实现完美转发,功能就是保持函数模板参数的原有属性(片面理解,这里涉及到引用折叠,稍后解释)。

将上面的代码进行整改:

void Print(int&& t)
{cout << t << endl;
}
template <class T>
void func(T &&t)// 即可以引用左值、也可引用右值
{Print(std::forward<T>(t));
}int main()
{func(15);// 传递右值return 0;
}

那么完美转发的应用场景是什么呢?这里给出一个场景:构造转发,即将构造函数设计成函数模板,使用完美转发将参数保持原样属性传递给其他对象的构造函数。

给出一段代码实现:

template <class Tname,class Tage,class Tsex>
User(Tname &&name, Tage &&age, Tsex &&sex):_name(std::forward<Tname>(name)),_age(std::forward<Tage>(age)),_sex(std::forward<Tsex>(sex))
{}

如上展示了一个User类的构造函数,暂且不需要关心_name、_age、_sex的具体类型。

反正在这个构造函数当中,name、age、sex所指向的对象都保持原有属性传递给了_name、_age和_sex的构造函数(如果指向的对象是右值的话,那么就是保持右值传递给移动构造)。

所以说,完美转发也可以解决不必要的拷贝问题。

6.引用折叠

引用总共就两种类型嘛,左值引用和右值引用。

注意看上面写的完美转发的函数模板,参数是"模板参数类型的右值引用"。那么模板参数类型无非就三种,非引用类型、左值引用、右值引用,所以引用折叠就是模板参数类型和后面所跟的&&结合起来:

template<calss T>
&&    // T不是引用类型
& &&    // T是左值引用
&& &&    // T是右值引用

在上面的伪代码中,可以得知:只有模板为左值引用的时候才会折叠成左值引用,其他的都是右值引用。

即,折叠后为"&&",右值引用;折叠后为"& &&",左值引用;折叠后为"&& &&",右值引用。

但是引用折叠是不能在代码当中体现的,它只能在一些间接转换的场景当中存在。例如:

int main()
{int x = 3;int& && rx = x;// 虽然这引用折叠最后会成为左值引用,但是不能在代码体现出来return 0;
}

上面的代码是会报错的。

所以可以得出几个结论:

  1. 如果传递的是右值,那么模板参数类型就为&&,最后折叠,函数模板的参数就为&&
  2. 如果传递的是左值,那么模板参数类型必须为&,最后折叠,函数模板的参数就为& &&,是一个左值引用。传递左值并且模板参数类型如果不是引用类型的话,那么折叠之后就是&&了,这是不对的

7.forward的原理

看一个例子:

template <class T>
void func(T &&t)// 即可以引用左值、也可引用右值
{Print(std::forward<T>(t));
}

再看一下forward的原型:

这里就很容易猜出forward的大概原理了。

拿上面的代码来说,如果传递给func的实参是右值,那么T的类型的就是&&,和后面的&&折叠, 最终是一个右值引用,那么func的完整写法就该是这样的:

template <class T>
void func(int&& &&t)// 假设实参是int类型
{Print(std::forward<T>(t));
}

但是要注意这样的写法是错误的,只是做一个演示。

虽然知道了t是一个右值引用,但是刚才说过,在函数内部只能当成左值来使用,所以在调用forward<T>的时候,匹配的是第一个重载函数。

在forward<T>方法内部,会进行一个类型转换:

template<typename T>
T&& forward(T &param)// forward的实现原理
{return static_cast<T&&>(param);
}

因为在调用forward的时候,即func的模板参数类型T为&&,所以forward的模板参数类型T也为&&。forward内部进行一个强制类型转换,param原本是个左值引用嘛,然后强转成&& &&类型,所以forward的返回值就是一个右值引用。这样就实现了forward保持参数原有属性的功能。

同样的,如果传递给func的实参就是一个左值,那么func、forward的模板参数类型T为&,所以在forward内部当中,所以param就强转成了& &&类型,是一个左值引用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/94169.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

hdu8-Congruences(中国剩余定理)

Problem - 7363 (hdu.edu.cn) 参考&#xff1a;2023杭电暑假多校8 题解 3 5 7 10 | JorbanS_JorbanS的博客-CSDN博客 题解&#xff1a;&#xff08;中国剩余定理 增量法&#xff09; 注意验证和特判&#xff0c;此题中 pi 两两互质&#xff0c;可用CRT和增量法&#xff0c;当…

Linux 进程替换

一、进程替换 把一个进程替换为另外一个进程。对于进程&#xff0c;如果单纯只看复制或者单纯只看替换&#xff0c;没有太大的意义。将复制和替换结合在一起&#xff08;forkexec&#xff09;&#xff0c;就是系统去产生一个全新进程的一种方式。 将复制和替换结合在一起&…

MySQL—缓存

目录标题 为什么要有Buffer Poolbuffer pool有多大buffer pool缓存什么 如何管理Buffer Pool如何管理空闲页如何管理脏页如何提高缓存命中率预读失效buffer pool污染 脏页什么时候会被刷入到磁盘 为什么要有Buffer Pool 虽然说MySQL的数据是存储在磁盘中&#xff0c;但是也不能…

爬虫IP时效问题:优化爬虫IP使用效果实用技巧

目录 1. 使用稳定的代理IP服务提供商&#xff1a; 2. 定期检测代理IP的可用性&#xff1a; 3. 配置合理的代理IP切换策略&#xff1a; 4. 使用代理IP池&#xff1a; 5. 考虑代理IP的地理位置和速度&#xff1a; 6. 设置合理的请求间隔和并发量&#xff1a; 总结 在爬虫过…

【JAVA】数组练习

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;浅谈Java &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 数组练习 1. 数组转字符串2. 数组拷贝3.…

Layui列表复选框根据条件禁用

// 禁用客服回访id有值的复选框res.data.forEach(function (item, i) {if (item.feedbackEmpId) {let index res.data[i][LAY_TABLE_INDEX];$(".layui-table tr[data-index"index"] input[typecheckbox]").prop(disabled,true);$(".layui-table tr[d…

c++--SLT六大组件之间的关系

1.SLT六大组件&#xff1a; 容器&#xff0c;迭代器&#xff0c;算法&#xff0c;仿函数&#xff0c;适配器&#xff0c;空间配置器 2.六大组件之间的关系 容器&#xff1a;容器是STL最基础的组件&#xff0c;没有容器&#xff0c;就没有数据&#xff0c;容器的作用就是用来存…

【ArcGIS Pro二次开发】(60):按图层导出布局

在使用布局导图时&#xff0c;会遇到如下问题&#xff1a; 为了切换图层和导图方便&#xff0c;一般情况下&#xff0c;会把相关图层做成图层组。 在导图的时候&#xff0c;如果想要按照图层组进行分开导图&#xff0c;如上图&#xff0c;想导出【现状图、规划图、管控边界】3…

【数据结构】 ArrayList简介与实战

文章目录 什么是ArrayListArrayList相关说明 ArrayList使用ArrayList的构造无参构造指定顺序表初始容量利用其他 Collection 构建 ArrayListArrayList常见操作获取list有效元素个数获取和设置index位置上的元素在list的index位置插入指定元素删除指定元素删除list中index位置上…

电商增强现实3D模型优化需要关注的4个方面

到目前为止&#xff0c;AR技术已经发展到足以在更广泛的范围内实施。 在电子商务中&#xff0c;这项技术有望提供更令人兴奋的购物体验。 为了实现这一目标&#xff0c;在这篇博客中&#xff0c;我将介绍如何针对电子商务中的 AR 优化 3D 模型。 推荐&#xff1a;用 NSDT编辑器…

企业计算机服务器中了360后缀勒索病毒怎么办,勒索病毒解密数据恢复

随着计算机技术的不断发展&#xff0c;企业的办公系统得到了很大提升&#xff0c;但是随之而来的网络安全威胁也不断增加&#xff0c;勒索病毒的攻击事件时有发生。近期&#xff0c;我们收到某地连锁超市的求助&#xff0c;企业的计算机服务器遭到了360后缀勒索病毒攻击&#x…

小程序具体开发

window 导航栏 属性名类型默认值作用navigationBarTitleText string字字符串导航栏标题内容navigationBarBackgroundColorHexcolor#000000设置导航栏背景颜色&#xff08;比如荧黄色 #ffa&#xff09;navigationBarTextStylestringwhite设置导航栏标题的颜色&#xff08;仅含有…

R语言实现神经网络(1)

#R语言实现神经网络 library(neuralnet) library(caret) library(MASS) library(vcd) data(shuttle) str(shuttle)#因变量use; table1<-structable(windmagn~use,shuttle) mosaic(table1,shadingT) mosaic(use~errorvis,shuttle) prop.table(table(shuttle$use,shuttle$stab…

Android Drawable转BitmapDrawable再提取Bitmap,Kotlin

Android Drawable转BitmapDrawable再提取Bitmap&#xff0c;Kotlin <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"…

【计算机网络篇】UDP协议

✅作者简介&#xff1a;大家好&#xff0c;我是小杨 &#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; UDP协议 1&#xff0c;UDP 简介 UDP&#xff08;User Datagram Protocol&#xff09;是一种无连…

绘制 PCA 双标图和碎石图

1、双标图 import numpy as np import matplotlib.pyplot as plt from sklearn.decomposition import PCA from sklearn.preprocessing import StandardScaler from sklearn import datasets# data np.random.random((1000,10)) # y np.random.randint(0,6,1000)iris datase…

OJ练习第149题—— 二叉树中的最大路径和

二叉树中的最大路径和 力扣链接&#xff1a;124. 二叉树中的最大路径和 题目描述 二叉树中的 路径 被定义为一条节点序列&#xff0c;序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点&#xff0c;且不一定经过根…

【刷题笔记8.17】LeetCode:最长公共前缀

LeetCode&#xff1a;最长公共前缀 &#xff08;一&#xff09;题目描述 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 &#xff08;二&#xff09;分析 纵向扫描时&#xff0c;从前往后遍历所有字符串的每一列&am…

设计模式之门面模式(Facade)的C++实现

1、门面模式提出 在组件的开发过程中&#xff0c;某些接口之间的依赖是比较紧密的&#xff0c;如果某个接口发生变化&#xff0c;其他的接口也会跟着发生变化&#xff0c;这样的代码违背了代码的设计原则。门面设计模式是在外部客户程序和系统程序之间添加了一层中间接口&…

【校招VIP】前端vue考点之生命周期和双向绑定

考点介绍&#xff1a; VUE是前端校招面试的重点&#xff0c;而生命周期和双向绑定又是基础考点之一&#xff0c;尤其在一二线公司&#xff0c;要求知道双向绑定的原理&#xff0c;以及相关代码实现。 『前端vue考点之生命周期和双向绑定』相关题目及解析内容可点击文章末尾链接…