[C++]引用折叠规则,std::move如何工作

引用折叠

我们知道,将一个左值传递给右值引用参数是不合法的,因为通常右值引用不能绑定到左值上,但是有两个例外。

  1. 右值引用指向模板类型参数,如 T&&,这种引用也叫做万能引用,因为它既可以被传入左值,也可以被传入右值。
template <typename T>
void f(T&& t) {}

当一个左值被传递给 T&& 参数时,如 int if(i) 进行调用,T 会被推断为 int&,而不是 int,这样 f 的参数就变成了 int& && 好像是一个引用的引用,这应该是不允许的啊。

事实上,我们确实不能直接定义一个引用的引用,但是通过模板参数间接定义是可以的。

  1. 如果定义出一个引用的引用,则这些引用会形成 折叠

折叠规则

对于一个给定的类型 X

  • X& &, X& &&, X&& & 会被折叠为 X&
  • X&& && 会被折叠为 X&&

只有右值引用的右值引用折叠后还是右值引用,其他只要沾上左值引用,最后都会被折叠为左值引用。

注意:引用折叠只能用于间接创建的引用的引用,如类型别名,或模板参数,auto。

所以,T&&参数,如果传入左值,会被折叠成 int&,传入右值,则会被折叠成 int&&,如果在 f 函数体内部需要将参数 t 传给其他具有万能引用的函数,那么由于右值引用和左值引用本身都是左值,传入到其他函数后会都被折叠成左值引用类型,如果要保持其引用类型不变,则需要进行完美转发。

move如何工作

我们常用 std::move 函数来将一个左值转换成右值,使用 move,即告诉编译器,我们希望将一个左值像一个右值一样处理它。

事实上,move 可以被传入任意类型的值,因为它是一个有万能引用的函数模板。

标准库中 move 是这样定义的:

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
	return static_cast<typename remove_reference<T>::type&&>(t);
}

remove_reference 是一个类模板。(详细文档:remove_reference - C++ Reference (cplusplus.com)

使用它可以将类型的引用去除,remove_reference 有一个模板类型参数和一个名为 type 的类型成员,如果我们使用一个引用类型实例化 remove_reference,则 type 将表示去除引用后的类型。如 remove_reference<int&>::type 就是 int 类型。

注意,使用时必须在前面加上 typename 以告知编译器 type 是一个类型成员。因为此时编译器不知道 remove_reference<T> 是一个什么样的类型,因为它还没有被实例化,其内部的 type 可能是 static 数据成员(编译器默认认为),也可能是类型成员,无法确定。需要程序员显式地使用 typename 告知才能通过编译。

static_cast用于强制类型转换,具体介绍在我的上一篇博客:[C++](27)强制类型转换 static_cast reinterpret_cast const_cast dynamic_cast_世真的博客-CSDN博客

如下两个例子,分别将右值和左值传入 move

string s1("hi!"), s2;
s2 = std::move(string("bye!")); //传入右值
s2 = std::move(s1);	//传入左值

第一个赋值(传入右值):

  • 函数参数为 T&&T 可以非常合理地被推断为 string

  • remove_reference<string>::type 非引用类型转换后不变,还是 string

  • 所以最后 move 被实例化为:

    string&& move(string&& t)
    {
    	return static_cast<string&&>(t);
    }
    
  • t 本身就是右值引用,所以强制类型转换什么也不做,最后返回的还是右值引用。

第二个赋值(传入左值):

  • 万能引用,编译器会将 T 推断为 string&

  • 函数参数 T&& 变成 string& && ,会折叠为 string&

  • remove_reference<string&>::type 就是 string

  • 所以最后 move 被实例化为:

    string&& move(string& t)
    {
    	return static_cast<string&&>(t);
    }
    
  • 这样,左值引用正好被转换成了右值引用。

这里的核心在于 static_cast,它可以将左值转换到右值引用。我们知道右值引用不能绑定到左值,这其实只是不允许进行隐式类型转换,而使用 static_cast 进行显式地转换是允许的。

此时,如果用一个对象 s2 来接收 move 的返回值,由于返回的是右值引用,那么就会调用 s2 的移动赋值运算符,将 s1 的资源转移给s2。

虽然允许我们自己使用static_cast将左值转换到右值类型,但还是建议使用标准库的 std::move,更加方便易读。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

世真

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值