C++完美转发与std::forward

初次遇到std::forward是在阅读std::make_shared源码时看到的,当时不少很能理解为什么这可以达到完美转发的目的。这里来总结一下。

引用折叠

std::forward的出现是因为C++11右值语义的出现,而有时候我们需要写下面的代码进行参数的转发。

template<class T>
void foo(T&& arg) {
    std::forward<T>(arg);
}

上面的函数看上去只能接受右值引用的参数,其实不然,在有了模板类型后,这里的&&成为了万能引用(Universal Reference),也就是说,上述函数既可以接收左值,也可以接收右值,但是在接收左值的时候,进行了引用折叠。

C++不允许reference to reference,所以会进行引用折叠,显然我们会出现下面四种情况。

  • Lvalue reference to Rvalue reference
  • Lvalue reference to Lvalue reference
  • Rvalue reference to Lvalue reference
  • Rvalue reference to Rvalue reference

其实规则很简单,只要其中一个引用类型为左值,该类型则为左值。

std::forward源码

一般的实现源码如下

template<class T>
T&& forward(typename std::remove_reference<T>::type& param)
{
    return static_cast<T&&>(param);
}

上面的代码具体意思就是如果我们传进来的T为左值类型,则返回的类型为左值,如果我们传进来的类型为右值类型,则返回的类型为右值。

这其中依赖的是模板的类型推导,用下面的代码示例说明引用折叠与std::forward的关系

int k = 1;
foo(k);
foo(2);

foo(k)由编译器实例化函数后调用了下面函数

void foo(int& &&);

这表面T类型被推导为了int&,所以std::forward函数的实例化也是int&类型的,所以我们的std::forward函数便转发了左值。

而我们调用foo(2)的时候,由编译器实例化调用了下面函数

void foo(int &&);

T类型被推导为了int,所以顺理成章std::forward函数的实例化为int类型的,所以返回了右值类型。

转发构造函数的参数

这样C++便完成了完美转发,而我们配合C++的可变长模板可以达成构造函数参数的转发,就再也不用写一大堆类型的转发函数了。

比如std::construct方法就可以直接通过转发参数进行构造对象

template<class T, class... Args>
void construct(T* p, Args&&... args) {
    new (p) T(std::forward<Args>(args)...);
}