【发布时间】:2014-09-04 03:45:30
【问题描述】:
【问题讨论】:
-
它们是正交的概念。最好问“什么是完美转发”,而不是问它们之间的区别。
-
@juanchopanza 确实是正交概念,不幸的是共享一个通用语法
T&&:(
标签: c++ c++11 move-semantics perfect-forwarding
【问题讨论】:
T&& :(
标签: c++ c++11 move-semantics perfect-forwarding
这个问题可能过于复杂,无法用简单的英文句子准确描述,但可以将完美转发视为一种将传递给函数的临时值移动到另一个函数的方法就好像第一个函数没有完全存在,因此没有任何不必要的副本或分配。 C++11 允许您通过在尝试获取引用(右值或左值)时在类型的右值 (&&) 和左值 (&) 引用之间引入一些转换规则来做到这一点其中。
R 值引用是 C++11 的一项功能,旨在解决移动语义和完美转发问题
这是简单的英文解释,但如果您想彻底理解问题,我建议您阅读以下内容:
我们希望将传递给函数F 的一些临时值传递给另一个函数E没有任何复制或赋值。
如果你尝试通过引用传递它
template<typename T> void F(T& a) { E(a); }
您将无法使用临时变量(它们不是左值)
F(1, 2, 3); // Won't work
将引用声明为const 会延长堆栈上临时对象的生命周期(这在历史上是为了避免常见的悬空引用错误),因此以下工作
template<typename T> void E(const T& a) {}
template<typename T> void F(const T& a) {
E(a);
}
但缺点是您必须修改函数的签名以符合此解决方案
如果我们对 E 的签名感兴趣(它应该符合某些东西)但对 F 的签名不感兴趣,我们可能会侥幸逃脱
template<typename T> void E(T& a) {}
template<typename T> void F(const T& a) {
E(const_cast<T&>(a));
}
但万一这被一个 real const 调用并且得到非常量,这将触发 undefined behavior
一个不可维护的解决方案可能是定义您需要的所有变体
template<typename T> void E(T& a) {}
template<typename T> void F(T& a) { E(a); }
template<typename T> void F(const T& a) { E(const_cast<T&>(a)); }
但是随着参数数量的增加,组合的数量也在增加:这很可能变得无法维护
C++11定义了一些规则说明
"[给定] 类型 TR 是对类型 T 的引用,尝试 创建类型“对 cv TR 的左值引用” 创建类型“左值 引用 T”,同时尝试创建类型“右值引用” to cv TR” 创建类型 TR。”
人类形态(TR = 对类型 T 的引用,R = 引用):
TR R
T& & -> T& // an lvalue reference to cv TR (becomes)-> lvalue reference to T
T& && -> T& // an rvalue reference to cv TR (becomes)-> TR (lvalue reference to T)
T&& & -> T& // an lvalue reference to cv TR (becomes)-> lvalue reference to T
T&& && -> T&& // an rvalue reference to cv TR (becomes)-> TR (rvalue reference to T)
这里重要的一点是,现在您可以跟踪函数接收到的类型:您可以接收一个左值并将相同的左值传递给 E,或者您可以接收一个 r -value 并将相同的 r 值(在将其转换后,因为对任何类型引用的左值引用成为左值引用)传递给 E:
template<typename T> void E(T&& a) {}
template<typename T> void F(T&& a) { E(static_cast<T&&>(a)); }
语法糖
static_cast<T&&>(a)
是
std::forward<T>(a); // is the same as static_cast<T&&>(a);
所以解决问题并使您的生活更轻松的最终代码是
template<typename T> void E(T&& a) {}
template<typename T> void F(T&& a) { E(std::forward<T>(a)); }
参考:Herb Sutter 的博客和其他一些资源,很遗憾我再也找不到了。如果有人对这些有任何线索,请将它们写在下面的 cmets 中,我会更新帖子。谢谢。
【讨论】:
template<typename T> void F(T&& a) { E(a); } 不也可以吗?通过说std::forward<T>(a);,我们向编译器提供了哪些额外信息?
处理右值引用和reference collapsing 可能比最初看起来更复杂。
完美转发
完美的转发是为了确保提供给函数的参数被转发(传递)到与originally provided具有相同值类别(基本上是右值与左值)的另一个函数。
它通常与可能发生引用折叠的模板函数一起使用。
也可以是used within the same function。
Scott Meyers 在他的 Going Native 2013 presentation 中给出了以下伪代码来解释 std::forward 的工作原理(大约 20 分钟);
template <typename T>
T&& forward(T&& param) { // T&& here is formulated to disallow type deduction
if (is_lvalue_reference<T>::value) {
return param; // return type T&& collapses to T& in this case
}
else {
return move(param);
}
}
示例
上面网站的一个例子,一个典型的例子是make_unique
template<class T, class... U>
std::unique_ptr<T> make_unique(U&&... u)
{
return std::unique_ptr<T>(new T(std::forward<U>(u)...));
}
在示例中,unique_ptr 的参数通过make_unique 提供给它,就像它们直接提供给unique_ptr 一样,即参数的引用、左值和右值性质得到维护。
一个更具体的例子;
#include <iostream>
#include <utility>
#include <memory>
struct A {
// implementation excluded
};
struct B {
B(A &) // ctor 1
{
std::cout << "ctor 1" << std::endl;
}
B(A&&) // ctor 2
{
std::cout << "ctor 2" << std::endl;
}
};
int main()
{
A a;
auto b1 = std::make_unique<B>(a); // ctor 1 is used
auto b2 = std::make_unique<B>(A()); // ctor 2 is used
}
简介
完美的转发取决于少数 C++11 新的基本语言结构,它们构成了我们现在在泛型编程中看到的大部分内容的基础:
std::forward 的使用目前是在公式化的std::forward<T> 中使用的,了解std::forward 的工作原理有助于理解为什么会这样,也有助于识别右值的非惯用或不正确使用、引用折叠等.
Thomas Becker 提供了一个很好但很密集的write up 关于完美的转发问题和解决方案。
【讨论】: