您当前的位置:首页 >>  热点 >  >> 
【重学C++】05 | 说透右值引用、移动语义、完美转发(下)
来源: 博客园      时间:2023-05-29 14:05:15
文章首发

【重学C++】05 | 说透右值引用、移动语义、完美转发(下)

引言

大家好,我是只讲技术干货的会玩code,今天是【重学C++】的第五讲,在第四讲《【重学C++】04 | 说透右值引用、移动语义、完美转发(上)》中,我们解释了右值和右值引用的相关概念,并介绍了C++的移动语义以及如何通过右值引用实现移动语义。今天,我们聊聊右值引用的另一大作用 -- 完美转发


【资料图】

什么是完美转发

假设我们要写一个工厂函数,该工厂函数负责创建一个对象,并返回该对象的智能指针。

template std::shared_ptr factory_v1(Arg arg){return std::shared_ptr(new T(arg));}class X1 {public:int* i_p;X(int a) {i_p = new int(a);}}

对于类X的调用方来说,auto x1_ptr = factory_v1(5);应该与auto x1_ptr = std::shared_ptr(new X1(5))是完全一样的。

也就是说,工厂函数factory_v1对调用者是透明的。要达到这个目的有两个前提:

传给factory_v1的入参arg能够完完整整(包括引用属性、const属性等)得传给T的构造函数。工厂函数factory_v1没有额外的副作用。

这个就是C++的完美转发。

单看factory_v1应用到X1貌似很"完美",但既然是工厂函数,就不能只满足于一种类对象的应用。假设我们有类X2。定义如下

class X2 {public:X2(){}X2(X2& rhs) {std::cout << "copy constructor call" << std::endl;}}

现在大家再思考下面代码:

X2 x2 = X2();auto x2_ptr1 = factory_v1(x2);// output:// copy constructor call// copy constructor callauto x2_ptr2 = std::shared_ptr(x2)// output:// copy constructor call

可以发现,auto x2_ptr1 = factory_v1(x2);auto x2_ptr2 = std::shared_ptr(x2)多了一次拷贝构造函数的调用。

为什么呢?很简单,因为factory_v1的入参是值传递,所以x2在传入factory_v1时,会调用一次拷贝构造函数,创建arg。很直接的办法,把factory_v1的入参改成引用传递就好了,得到factory_v2

template std::shared_ptr factory_v2(Arg& arg){return std::shared_ptr(new T(arg));}

改成引用传递后,auto x1_ptr = factory_v2(5);又会报错了。因为factory_v2需要传入一个左值,但字面量5是一个右值。

方法总比困难多,我们知道,C++的const X&类型参数,既能接收左值,又能接收右值,所以,稍加改造,得到factory_v3

template std::shared_ptr factory_v3(const Arg& arg){return std::shared_ptr(new T(arg));}

factory_v3还是不够"完美", 再看看另外一个类X3

class X3 {public:X3(){}X3(X3& rhs) {std::cout << "copy constructor call" << std::endl;}X3(X3&& rhs) {std::cout << "move constructor call" << std::endl;}}

再看看以下使用例子

auto x3_ptr1 = factory_v3(X3());// output// copy constructor callauto x3_ptr2 = std::shared_ptr(new X3(X3()));// output// move constructor call

通过上一节我们知道,有名字的都是左值,所以factory_v3永远无法调用到T的移动构造函数。所以,factory_v3还是不满足完美转发。

特殊的类型推导 - 万能引用

给出完美转发的解决方案前,我们先来了解下C++中一种比较特殊的模版类型推导规则 - 万能引用。

// 模版函数签名template void foo(ParamType param);// 应用foo(expr);

模版类型推导是指根据调用时传入的expr,推导出模版函数fooParamTypeparam的类型。

类型推导的规则有很多,大家感兴趣可以去看看《Effective C++》[1],这里,我们只介绍一种比较特殊的万能引用。 万能引用的模版函数格式如下:

templatevoid foo(T&& param);

万能引用的ParamTypeT&&,既不能是const T&&,也不能是std::vector&&

万能引用的规则有三条:

如果expr是左值,Tparam都会被推导成左值引用。如果expr是右值,T会被推导成对应的原始类型,param会被推导成右值引用(注意,虽然被推导成右值引用,但由于param有名字,所以本身还是个左值)。在推导过程中,expr的const属性会被保留下来。

看下面示例

templatevoid foo(T&& param);// x是一个左值int x=27;// cx是带有const的左值const int cx = x;// rx是一个左值引用const int& rx = cx;// x是左值,所以T是int&,param类型也是int&foo(x);// cx是左值,所以T是const int&,param类型也是const int&foo(cx);// rx是左值,所以T是const int&,param类型也是const int&foo(rx);// 27是右值,所以T是int,param类型就是int&&foo(27);
std::forward实现完美转发

到此,完美转发的前置知识就已经讲完了,我们看看C++是如何利用std::forward实现完美转发的。

template std::shared_ptr factory_v4(Arg&& arg){   return std::shared_ptr(new T(std::forward(arg)));}

std::forward的定义如下

templateS&& forward(typename remove_reference::type& a) noexcept{  return static_cast(a);}
传入左值
X x;auto a = factory_v4(x);

根据万能引用的推导规则,factory_v4中的Arg会被推导成X&。这个时候factory_v4std::forwrd等价于:

shared_ptr factory_v4(X& arg){   return shared_ptr(new A(std::forward(arg)));}X& std::forward(X& a) {  return static_cast(a);}

这个时候传给A的参数类型是X&,即调用的是拷贝构造函数A(X&)。符合预期。

传入右值
X createX();auto a = factory_v4(createX());

根据万能引用推导规则,factory_v4中的Arg会被推导成X。这个时候factory_v4std::forwrd等价于:

shared_ptr factory_v4(X&& arg){   return shared_ptr(new A(std::forward(arg)));}X&& forward(X& a) noexcept{  return static_cast(a);}

此时,std::forward作用与std::move一样,隐藏掉了arg的名字,返回对应的右值引用。这个时候传给A的参数类型是X&&,即调用的是移动构造函数A(X&&),符合预期。

总结

这篇文章,我们主要是继续第四讲的内容,一步步学习了完美转发的概念以及如何使用右值解决参数透传的问题,实现完美转发。

[1] https://github.com/CnTransGroup/EffectiveModernCppChinese/blob/master/src/1.DeducingTypes/item1.md

END

【往期推荐】

【重学C++】01| C++ 如何进行内存资源管理?

【重学C++】02 | 脱离指针陷阱:深入浅出 C++ 智能指针

【重学C++】03 | 手撸C++智能指针实战教程

【重学C++】04 | 说透C++右值引用、移动语义、完美转发(上)

标签:
  • “谢谢选择我做你的妈妈!” 这封信请18年后查收

      “谢谢选择我做你的妈妈!” 这封信请18年后查收  扬子晚报讯(通讯员 刘威 记者 朱鼎兆)小时候,母亲常常在家里给我们留字条,

    来源:      时间:2022-05-09
  • 跟新冠病毒“赛跑” 他要让机器人完成核酸检测

      跟新冠病毒“赛跑” 他要让机器人完成核酸检测  经常学生们还不知道我怎么想的时候,我就把自己否定了。工作中需要有自我否定的勇气

    来源:      时间:2022-05-09
  • 助力无接触配送 上海无人车“上岗”

      助力无接触配送 上海无人车“上岗”  【疫情防控新举措】  科技日报讯 (记者符晓波)眼下,上海疫情蔓延趋势得到有效控制,不少

    来源:      时间:2022-05-09
  • “态靶辨治” 帮助患者快速转阴

      “态靶辨治” 帮助患者快速转阴  近日,随着患者清零,吉林省长春市北湖奥体中心篮球馆方舱医院等多个方舱陆续“休舱”,各医疗队也

    来源:      时间:2022-05-09
  • 四省市联合医疗队为患者全方位“解忧”

      四省市联合医疗队为患者全方位“解忧”  【同心守沪抗疫】  在上海城市足迹馆定点医院的宣传墙上,各类慢性病、基础病的健康宣教手

    来源:      时间:2022-05-09
  • 周美亮: 搜寻野生荞麦的“追种人”

      周美亮: 搜寻野生荞麦的“追种人”  ◎本报记者 马爱平  一走进位于国家作物种质库新库内的中国农业科学院作物科学研究所研究员

    来源:      时间:2022-05-09
  • 防晒“神器”竟是珊瑚“杀手”

      防晒“神器”竟是珊瑚“杀手”  科技日报北京5月8日电 (实习记者张佳欣)珊瑚礁是地球上生物最丰富、最具经济价值的生态系统之一。

    来源:      时间:2022-05-09

X 关闭

X 关闭

Copyright   2015-2022 亚太娱乐网版权所有   备案号:沪ICP备2020036824号-11   联系邮箱: 562 66 29@qq.com