移动语义与完美转发的区别

Move semantics and perfect forwarding difference

本文关键字:区别 转发 完美 语义 移动      更新时间:2023-10-16

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

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

谁能用简单的英语和简单的例子解释一下什么是完全转发?

纯英文尝试

这个问题可能太复杂了,无法用简单的英语句子准确地描述,但是可以把完美转发看作是一种将传递给函数的临时值移动到另一个函数的方法,就好像第一个函数根本不存在一样,因此没有任何不必要的复制或赋值。c++ 11允许您在试图从中获取引用(r值或l值)时,通过引入一些r值(&&)和l值(&)对类型的引用之间的转换规则来实现这一点。

r值引用是c++ 11的一个特性,它们被设计用来解决移动语义和完美的转发问题

这是一个简单的英语解释,但如果你想彻底理解这个问题,我建议你阅读以下内容:

<标题>问题:

我们希望传递给函数F的一些临时值可以传递给另一个函数E ,而不需要复制或赋值

尝试解决此问题

  • 如果你试图通过引用传递它,比如

    template<typename T> void F(T& a) { E(a); }
    

    你将不能使用临时值(它们不是l值)

    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调用它并得到非常量,那将触发未定义行为

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

    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"创建类型"左值",而试图创建类型"右值引用"to cv TR "创建类型TR。"

in human-form (TR = type T的引用,R = reference):

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)
这里重要的一点是,现在你可以跟踪函数接收到的类型:你可以接收到一个l值并将相同的l值传递给E,或者你可以接收到一个r值并将相同的r值传递给E(在将l值引用转换为任何类型引用之后):

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的博客和其他一些资源,不幸的是我再也找不到了。如果有人有关于这些的线索,请写在下面的评论中,我会更新帖子。谢谢。

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

完美转发

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

通常用于可能发生引用折叠的模板函数。

也可在同一函数中使用。

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一样,也就是说,参数的引用、l值和r值性质是保持不变的。

一个更具体的例子;

#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中一些新的基本语言结构,这些结构构成了我们现在在泛型编程中看到的大部分内容的基础:

  • 参考崩溃
  • 右值引用
  • <
  • 移动语义/gh>

std::forward的使用目前是在公式std::forward<T>中使用的,理解std::forward的工作原理有助于理解为什么会这样,还有助于识别非惯用的或不正确的右值使用,引用折叠和类似。

Thomas Becker提供了一篇关于完美转发问题和解决方案的好文章。