为了账号安全,请及时绑定邮箱和手机立即绑定

std :: forward如何工作?

std :: forward如何工作?

C++
噜噜哒 2019-11-22 12:35:01
我知道它的作用以及何时使用它,但是我仍然无法确定它是如何工作的。请尽可能详细,并说明std::forward如果允许使用模板参数推导的话,什么时候不正确。我的部分困惑是:“如果有名称,则是左值”-如果是这种情况,为什么std::forward在通过thing&& xvs 时行为会有所不同thing& x?
查看完整描述

3 回答

?
拉丁的传说

TA贡献1789条经验 获得超8个赞

首先,让我们看一下std::forward根据标准执行的操作:


§20.2.3 [forward] p2


返回值: static_cast<T&&>(t)


(这T是显式指定的模板参数,t它是传递的参数。)


现在,请记住参考折叠规则:


TR   R


T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T

T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)

T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T

T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

(从这个答案中偷偷偷走了。)


然后,让我们看一下一个想要使用完美转发的类:


template<class T>

struct some_struct{

  T _v;

  template<class U>

  some_struct(U&& v)

    : _v(static_cast<U&&>(v)) {} // perfect forwarding here

                                 // std::forward is just syntactic sugar for this

};

现在是一个示例调用:


int main(){

  some_struct<int> s1(5);

  // in ctor: '5' is rvalue (int&&), so 'U' is deduced as 'int', giving 'int&&'

  // ctor after deduction: 'some_struct(int&& v)' ('U' == 'int')

  // with rvalue reference 'v' bound to rvalue '5'

  // now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int&&>(v)'

  // this just turns 'v' back into an rvalue

  // (named rvalue references, 'v' in this case, are lvalues)

  // huzzah, we forwarded an rvalue to the constructor of '_v'!


  // attention, real magic happens here

  int i = 5;

  some_struct<int> s2(i);

  // in ctor: 'i' is an lvalue ('int&'), so 'U' is deduced as 'int&', giving 'int& &&'

  // applying the reference collapsing rules yields 'int&' (& + && -> &)

  // ctor after deduction and collapsing: 'some_struct(int& v)' ('U' == 'int&')

  // with lvalue reference 'v' bound to lvalue 'i'

  // now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int& &&>(v)'

  // after collapsing rules: 'static_cast<int&>(v)'

  // this is a no-op, 'v' is already 'int&'

  // huzzah, we forwarded an lvalue to the constructor of '_v'!

}

我希望这个循序渐进的答案可以帮助您和其他人了解其std::forward工作原理。


查看完整回答
反对 回复 2019-11-22
?
繁星点点滴滴

TA贡献1803条经验 获得超3个赞

我认为std::forwardas 的解释static_cast<T&&>令人困惑。我们对转换的直觉是将类型转换为其他类型-在这种情况下,它将是对右值引用的转换。不是!因此,我们正在使用另一种神秘事物来解释一种神秘事物。这种特殊的演员表由Xeo的答案中的表格定义。但是问题是:为什么?所以这是我的理解:


假设我想向您传递一个std::vector<T> v您应该作为数据成员存储在数据结构中的数据_v。天真的(和安全的)解决方案是始终将向量复制到其最终目的地。因此,如果通过中间函数(方法)执行此操作,则应将该函数声明为引用。(如果将其声明为按值获取向量,则将执行其他完全不必要的复制。)


void set(const std::vector<T> & v) { _v = v; }

如果您手里有一个左值,这一切都很好,但是右值呢?假设向量是调用函数的结果makeAndFillVector()。如果您执行直接分配:


_v = makeAndFillVector();

编译器将移动向量而不是复制向量。但是,如果您引入中介,set()则有关您的论据右值性质的信息将丢失,并将进行复制。


set(makeAndFillVector()); // set will still make a copy

为了避免这种复制,您需要“完美转发”,这将每次都能获得最佳代码。如果给定左值,则希望函数将其视为左值并进行复制。如果给定了右值,则希望函数将其视为右值并将其移动。


通常,您可以通过set()分别为lvalues和rvalues 重载函数来做到这一点:


set(const std::vector<T> & lv) { _v = v; }

set(std::vector<T> && rv) { _v = std::move(rv); }

但是现在想象一下,您正在编写一个接受T和调用set()该函数的模板函数T(不必担心我们set()仅为向量定义的事实)。诀窍在于,set()当模板函数使用左值实例化时,您希望此模板调用第一个版本,而当使用右值初始化时,则希望该模板调用第二个版本。


首先,该函数的签名应该是什么?答案是这样的:


template<class T>

void perfectSet(T && t);

根据调用此模板函数的方式,T将在某种程度上以不同的方式神奇地推断出类型。如果使用左值调用它:


std::vector<T> v;

perfectSet(v);

向量v将通过引用传递。但是,如果使用右值调用它:


perfectSet(makeAndFillVector());

(匿名)向量将通过右值引用传递。因此,故意设置C ++ 11魔术以尽可能保留参数的右值性质。


现在,在perfectSet内部,您希望将参数完美地传递给的正确重载set()。这std::forward是必需的:


template<class T>

void perfectSet(T && t) {

    set(std::forward<T>(t));

}

如果没有std :: forward,编译器将不得不假定我们要通过引用传递t。为了使自己确信这是真的,请比较以下代码:


void perfectSet(T && t) {

    set(t);

    set(t); // t still unchanged

}

对此:


void perfectSet(T && t) {

    set(std::forward<T>(t));

    set(t); // t is now empty

}

如果您未明确转发t,则编译器必须防御性地假设您可能会再次访问t并选择set的左值引用版本。但是,如果转发t,则编译器将保留其右值性,并且set()将调用的右值引用版本。此版本移动的内容t,这意味着原始内容为空。


这个答案比我最初认为的要长得多;-)


查看完整回答
反对 回复 2019-11-22
  • 3 回答
  • 0 关注
  • 613 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信