万能引用与完美转发

万能引用

注意:当&&出现在模板中时,不是右值引用,而是叫万能引用。比如下面的T&& t

template<typename T>
void PerfectForward(T&& t)
{
 Fun(t);
}

万能引用:

  • 当实参是左值,那么T&& t就是个左值引用

    有些地方,也会叫做引用折叠

    什么意思呢?就是原本形参是T&& t,但比如当实参是个int类型的变量,那么形参这里的模板参数就会变成:int& t。原本两个&&,现在变成一个&。因此形象的称为是引用折叠

  • 当实参是右值,那么T&& t就是个右值引用

为什么会有万能引用呢?归根结底是因为,这里的T是通过形参t推导出来的,假如实参是10,那么T就是int&&;假如实参是int类型的变量,那么T就是int&

完美转发

  • 问题引出

我们看如下代码:

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }

template<typename T>
void PerfectForward(T&& t)
{
  Fun(t);
}

int main()
{
 PerfectForward(10);           // 右值
 int a;
 PerfectForward(a);            // 左值
 PerfectForward(std::move(a)); // 右值
 const int b = 8;
 PerfectForward(b);            // const 左值
 PerfectForward(std::move(b)); // const 右值
 return 0;
}

打印结果:在这里插入图片描述

为什么打印结构都是左值引用呢?第一、三、五不都传的是右值吗?

  • 原因分析

关键问题出在了这里:T&& t

右值引用会被编译器识别为左值。即这里的t是个左值

因此,在void PerfectForward(T&& t)中,实参传递到t中,不论实参是右值还是左值,t始终是个左值。然后再Fun(t);,调用的也当然都是左值引用的函数

  • 深入思考

假如我想要实参是右值t就是右值,实参是左值t就是左值呢?那么就需要用到完美转发

完美转发:在传参的过程中保留对象原生类型属性。完美转发的实现需要用到库里面的函数forward()

也就是说,右值引用会被默认识别为左值,如果想让其保持原本的属性,那么就用完美转发

对上面的例子,加上完美转发后,结果如下:

在这里插入图片描述

完美转发的应用场景

看如下例子:(右值引用中的场景二)

int main()
{
	list<xy::string> it;
	it.push_back("111");

	return 0;
}

C++11之前, 上述代码的调用过程:

在这里插入图片描述

C++11后, “111”是个右值,因此在红框处可以移动拷贝。所以调用过程变成了下面:

在这里插入图片描述

到这里就和我们在右值引用中所讲的过程一样。但是其实上述代码是存在问题的

这是因为:首先"111"传给push_back(T&& x)没有问题,但此时x默认是个左值,然后insert(end(),x)这一调用,并不能调用到右值引用版本的insert,后面的Node(x)也不会调用到右值引用版本的list_node构造函数。所以最终也不是移动拷贝,而是深拷贝

完美转发的出现,很好的解决了上述问题,完美只需如下修改:

在这里插入图片描述

让每个右值引用形参都保留其原本属性,这样最终就可以调用到移动拷贝

万能引用的一个小点

在完美转发的应用场景中:

list_node的构造函数会有两个版本,一个是左值引用的版本,一个是右值引用的版本:

template<class T>
struct list_node
{
    list_node<T>* _prev;
    list_node<T>* _next;
    T _val;

    list_node(const T& val)
        :_prev(nullptr)
        , _next(nullptr)
        , _val(val)
    {}

    list_node(T&& val)
        : _prev(nullptr)
        , _next(nullptr)
        , _val(forward<T>(val))
    {}
};

在万能引用中说到:模板中出现&&不是右值引用,而是万能引用。那这里的list_node(T&& val)是万能引用吗?如果是的话,那么list_node(const T& val)这个左值引用的函数是不是就没有存在的必要了?

其实这里不是万能引用,list_node(const T& val)也不能删去

因为我们说的万能引用最核心的因素在于,模板参数T是可以通过形参val推出来的,而这里的T并不是通过val推导出来的,而是在这里在这里插入图片描述
就确定了的

但是我们只需如下:

template<class T>
struct list_node
{
    list_node<T>* _prev;
    list_node<T>* _next;
    T _val;

    template<class Ty>
    list_node(Ty&& val)
        : _prev(nullptr)
        , _next(nullptr)
        , _val(forward<Ty>(val))
    {}
};

那么就可以使得其变成万能引用(类模板中再用函数模板)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值