9

我已经从这个问题中得到了什么是移动语义: 什么是移动语义?

但是我仍然不明白与移动语义相关的完美转发是什么。

有人可以用简单的英语和一个简单的例子解释完美转发的含义吗?

4

2 回答 2

16

纯英语尝试

这个问题可能太复杂了,无法用简单的英语句子准确描述,但可以将完美转发视为一种将传递给函数的临时值移动到另一个函数的方法,就好像第一个函数根本不存在一样,所以没有任何不必要的副本或分配。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));
    }
    

    但是如果这被一个真正的 const调用并且得到非常数,那将触发未定义的行为

  • 一个不可维护的解决方案可能是定义您需要的所有变体

    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 中的解决方案

C++11 定义了一些规则

“[给定] 类型 TR 是对类型 T 的引用,尝试创建类型“对 cv TR 的左值引用”会创建类型“对 T 的左值引用”,而尝试创建类型“对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,或者您可以接收一个右值并传递相同的右值(在将它转换为 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 的博客和其他一些资源,很遗憾我再也找不到了。如果有人对此有任何线索,请将它们写在下面的评论中,我会更新帖子。谢谢。

于 2014-07-14T09:46:39.107 回答
3

处理 r 值引用和引用折叠可能比最初看起来更复杂。

完美转发

完美的转发是为了确保提供给函数的参数被转发(传递)到另一个与最初提供的具有相同值类别(基本上是 r-value 与 l-value)的函数。

它通常与可能发生引用折叠的模板函数一起使用。

它也可以在同一个函数内使用

Scott Meyers 在他的Going Native 2013 演示文稿中给出了以下伪代码来解释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 提供了一篇关于完美转发问题和解决方案的精彩但密集的文章。

于 2014-07-14T09:27:04.597 回答