浅析完美转发

 因此,我们需要抓住的关键词是转发,将一个或多个实参不变地转发给其他函数,需要抓住的对象是其他函数,还要抓住的一个关键词是实参。并且我们还要牢记,完美转发是在模板编程中的概念,一般用在模板编程中!

为什么说不变地呢?难道不用完美转发就会在将实参转发给其他函数地时候发生变化嘛,变的又是什么呢? 在下面,我们将给出一个例子,指明一些做法存在的问题,使其不能达到完美转发,而使用std::forward便能是我们达成完美转发!

我们来看看下面的代码:

#include<iostream>

void func1(int&& arg1)
{
    func2(arg1);
}

void func2(int&& arg2);

上面的代码符合我们上面说的情景,想将func1函数里的实参转发给func2。

但是上面的代码其实是编译不通过的,看看报错信息:

 如果对左值引用和右值引用的转换不是很清晰的同学可能就会迷糊了,arg1不是右值引用类型的嘛,func2接受的参数也是右值引用类型的呀!

但是,函数参数与其他任何变量一样,都是左值表达式(或者说移动语义不会传递!这么设计是故意的,因为如果移动语义可以传递的话,你在第一次使用右值对象时就失去了它的值)!所以不能将左值引用传递给右值所以报错!所以这里我们的实参发生了变化,变的是从右值引用变成了左值引用!

下面我们便使用forward函数来避免这一问题,我们简单修改一下代码:

#include<iostream>

void func1(int&& arg1)
{
    func2(std::forward<int&&>(arg1));
}

void func2(int&& arg2);

这下是编译通过的!我们通过给arg1加上forward便能使其从左值引用变换回右值引用!这样就达成了我们上面说的完美转发!也就是:如果我们的wrapper函数的参数传递的是左值,那么内部函数调用时得到的就是左值,如果我们的wrapper函数的参数传递的是右值,那么内部函数调用时得到的就是右值。

关于forward函数的具体原理,实际上和 move 函数差不多,用到了完美引用和引用折叠,使得不管传进来的是左值引用还是右值引用,最后得到的都是模板类型所指定的那个类型!(所以这里不用 forward 用 move 也是一样的,因为我们forward右值和move是一样的效果)

事实上,我们上面说到的将一个函数的参数转发给另一个函数的场景还是挺常见的,比如下面的工厂模式代码(稍微熟悉一点设计模式的同学应该都看得懂)

 下面,我们就用上面这个函数,演示一下模板编程中的完美转发!

上面的函数现在并不完美,因为其函数参数只是形参,会调用函数时进行拷贝构造,于是我们可以加上引用符号,这样性能就会提高一些。

#include<iostream>
#include <memory>
using namespace std;


template<typename T, typename Arg>
shared_ptr<T> factory(Arg& arg)
{
    return shared_ptr<T>(new T(arg));
}

但是仍存在问题,以上代码不能传递右值,于是我们可以重载一个const引用 (const引用能接受右值)

#include<iostream>
#include <memory>
using namespace std;


template<typename T, typename Arg>
shared_ptr<T> factory(Arg& arg)
{
    return shared_ptr<T>(new T(arg));
}

template<typename T, typename Arg>
shared_ptr<T> factory(Arg const& arg)
{
    return shared_ptr<T>(new T(arg));
}

但是仍存在问题,这里只是一个参数,如果存在多个参数的话,而且如果有的参数需要左值有的需要右值,那你就要写多个重载函数(排列组合了),实在是不好。

接下来,我们就用上面说的完美转发来实现一下模板编程里的完美转发:

#include<iostream>
#include <memory>
using namespace std;


template<typename T, typename Arg>
shared_ptr<T> factory(Arg&& arg)
{
    return shared_ptr<T>(new T(std::forward<Arg>(arg)));
}

首先我们的函数参数为 Arg&& arg 使用了引用折叠使得可以匹配任意左值或右值,这样就解决上上面说的重载问题。其次我们使用了std::forward解决了更上面说的参数转发问题!这样就达成了完美转发!

下面让我们来验证一下:

#include<iostream>
#include <memory>
using namespace std;


class CTemp
{
    int m_iData;
public:
    CTemp(int& arg):m_iData(arg)
    {
        cout << "Ctemp(int& arg)" << endl;
    }
    CTemp(int&& arg):m_iData(arg)
    {
        cout << "Ctemp(int&& arg)" << endl;
    }
};


template<typename T, typename Arg>
shared_ptr<T> factory(Arg&& arg)
{
    return shared_ptr<T>(new T(std::forward<Arg>(arg)));
}

int main(int argc, char* argv[])
{
    int value = 5;
    auto p1 = factory<CTemp>(5);
    auto p2 = factory<CTemp>(value);
    return 0;
}

运行结果:

所以,可以看见,我们使用了完美转发后可以匹配任意左值或者右值!

参考资料:

C++ Primer第五版

C++新标准002_动动小手指就能实现 " 完美转发 "_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值