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工作原理。
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,这意味着原始内容为空。
这个答案比我最初认为的要长得多;-)
- 3 回答
- 0 关注
- 613 浏览
添加回答
举报