移动语义并完善转发差异

时间:2014-07-14 09:01:00

标签: c++ c++11 move-semantics perfect-forwarding

我已经从这个问题得到了什么样的移动语义: What are move semantics?

但是我仍然没有得到与移动语义相关的完美转发。

有人可以用简单的英语和一个简单的例子解释完美的转发意味着什么吗?

2 个答案:

答案 0 :(得分:14)

纯英文版

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

    但是如果使用真实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“,同时尝试创建”rvalue引用类型   到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)

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

Live example


参考文献:Herb Sutter的博客和一些其他来源,遗憾的是我再也找不到了。如果有人对这些有任何疑问,请在下面的评论中写下来,我会更新帖子。感谢。

答案 1 :(得分:3)

处理r值引用和reference collapsing可能比最初出现的更复杂。

完美转发

完美转发是为了确保提供给函数的参数被转发(传递)到具有相同值类别(基本上是r值vs l值)的另一个函数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,即引用,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新增的一些基本语言结构,这些结构构成了我们现在在泛型编程中看到的大部分基础:

  • 参考折叠
  • Rvalue references
  • 移动语义

std::forward目前用于公式化std::forward<T>,了解std::forward的工作方式有助于理解为何如此,并有助于识别非惯用或不正确使用价值,参考崩溃和同类。

Thomas Becker在完美的转发问题和解决方案方面提供了一个很好但密集的write up