Ste*_*eng 411 c++ c++-faq rvalue-reference perfect-forwarding c++11
在完美转发中,std::forward用于转换命名的右值引用t1和t2未命名的右值引用.这样做的目的是什么?inner如果我们离开t1&t2作为左值,那将如何影响被调用的函数?
template <typename T1, typename T2>
void outer(T1&& t1, T2&& t2)
{
inner(std::forward<T1>(t1), std::forward<T2>(t2));
}
Run Code Online (Sandbox Code Playgroud)
GMa*_*ckG 754
您必须了解转发问题.你可以详细阅读整个问题,但我会总结一下.
基本上,给定表达式E(a, b, ... , c),我们希望表达式f(a, b, ... , c)是等价的.在C++ 03中,这是不可能的.有许多尝试,但它们在某些方面都失败了.
最简单的是使用左值引用:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
E(a, b, c);
}
Run Code Online (Sandbox Code Playgroud)
但是这无法处理临时值:f(1, 2, 3);因为那些不能绑定到左值引用.
下一次尝试可能是:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(a, b, c);
}
Run Code Online (Sandbox Code Playgroud)
这解决了上面的问题,但翻转了翻牌.现在它不允许E有非const参数:
int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these
Run Code Online (Sandbox Code Playgroud)
第三次尝试接受const引用,但接着const_cast是const:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}
Run Code Online (Sandbox Code Playgroud)
这接受所有值,可以传递所有值,但可能导致未定义的行为:
const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!
Run Code Online (Sandbox Code Playgroud)
最终解决方案正确处理所有事情......代价是无法维护.您提供的过载f,与所有的常量和非const的组合:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);
Run Code Online (Sandbox Code Playgroud)
N个参数需要2 N组合,这是一场噩梦.我们想自动执行此操作.
(这实际上是我们让编译器在C++ 11中为我们做的.)
在C++ 11中,我们有机会解决这个问题.一种解决方案修改现有类型的模板推导规则,但这可能会破坏大量代码.所以我们必须找到另一种方式.
解决方案是使用新添加的rvalue-references ; 我们可以在推导rvalue-reference类型并创建任何所需结果时引入新规则.毕竟,我们现在不可能破坏代码.
如果给出对引用的引用(注释引用是包含两个T&和的含义术语T&&),我们使用以下规则来计算结果类型:
"[给定]一个类型TR,它是对类型T的引用,尝试创建类型"左值引用cv TR"创建类型"左值引用T",同时尝试创建类型"rvalue引用到cv TR"创建TR类型".
或者以表格形式:
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)
Run Code Online (Sandbox Code Playgroud)
接下来,使用模板参数推导:如果参数是左值A,我们提供带有左值引用的模板参数A.否则,我们正常推导.这给出了所谓的通用引用(术语转发引用现在是官方引用).
为什么这有用?因为组合我们保持跟踪类型的值类别的能力:如果它是左值,我们有一个左值引用参数,否则我们有一个rvalue-reference参数.
在代码中:
template <typename T>
void deduce(T&& x);
int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)
Run Code Online (Sandbox Code Playgroud)
最后一件事是"转发"变量的值类别.请记住,一旦在函数内部,参数可以作为左值传递给任何东西:
void foo(int&);
template <typename T>
void deduce(T&& x)
{
foo(x); // fine, foo can refer to x
}
deduce(1); // okay, foo operates on x which has a value of 1
Run Code Online (Sandbox Code Playgroud)
那不好.E需要获得与我们相同的价值类别!解决方案是这样的:
static_cast<T&&>(x);
Run Code Online (Sandbox Code Playgroud)
这是做什么的?考虑一下我们在deduce函数内部,并且我们已经传递了一个左值.这意味着T是一个A&,所以静态强制转换的目标类型是A& &&,或者只是A&.既然x已经是A&,我们什么都不做,并留下左值参考.
当我们传递一个右值时,T是A,所以静态强制转换的目标类型是A&&.转换导致rvalue表达式,该表达式不能再传递给左值引用.我们维护了参数的值类别.
把这些放在一起给了我们"完美的转发":
template <typename A>
void f(A&& a)
{
E(static_cast<A&&>(a));
}
Run Code Online (Sandbox Code Playgroud)
当f接收到一个左值,E得到一个左值.当f收到一个右值时,E得到一个右值.完善.
当然,我们想要摆脱丑陋.static_cast<T&&>要记住是神秘而奇怪的; 让我们改为调用一个实用函数forward,它执行相同的操作:
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
Run Code Online (Sandbox Code Playgroud)
use*_*610 56
我认为有一个实现std :: forward的概念代码可以添加到讨论中.这是来自Scott Meyers的一篇幻灯片,讲述了一个有效的C++ 11/14采样器

move代码中的函数是std::move.在那次谈话中,它有一个(工作)实现.我在libstdc ++中找到了std :: forward的实际实现,在文件move.h中,但它没有任何指导意义.
从用户的角度来看,它的含义是std::forward对rvalue的条件转换.如果我正在编写一个函数,该函数需要参数中的左值或右值,并且只有当它作为右值传入时才想将它作为右值传递给另一个函数.如果我没有将参数包装在std :: forward中,它将始终作为普通引用传递.
#include <iostream>
#include <string>
#include <utility>
void overloaded_function(std::string& param) {
std::cout << "std::string& version" << std::endl;
}
void overloaded_function(std::string&& param) {
std::cout << "std::string&& version" << std::endl;
}
template<typename T>
void pass_through(T&& param) {
overloaded_function(std::forward<T>(param));
}
int main() {
std::string pes;
pass_through(pes);
pass_through(std::move(pes));
}
Run Code Online (Sandbox Code Playgroud)
果然,它打印出来
std::string& version
std::string&& version
Run Code Online (Sandbox Code Playgroud)
该代码基于前面提到的谈话中的示例.从开始大约15:00滑10.
sel*_*tze 29
在完美转发中,std :: forward用于将命名的右值引用t1和t2转换为未命名的右值引用.这样做的目的是什么?如果我们将t1和t2保留为左值,那会对被调用函数内部产生什么影响?
Run Code Online (Sandbox Code Playgroud)template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) { inner(std::forward<T1>(t1), std::forward<T2>(t2)); }
如果在表达式中使用命名的右值引用,它实际上是一个左值(因为您按名称引用了对象).请考虑以下示例:
void inner(int &, int &); // #1
void inner(int &&, int &&); // #2
Run Code Online (Sandbox Code Playgroud)
现在,如果我们称outer这样的
outer(17,29);
Run Code Online (Sandbox Code Playgroud)
我们希望将17和29转发到#2,因为17和29是整数文字和rvalues.但由于t1和t2表达式inner(t1,t2);是左值,你可以调用#1,而不是#2.这就是为什么我们需要将引用转回到未命名的引用中std::forward.因此,t1in outer始终是左值表达式,而forward<T1>(t1)可能是rvalue表达式,具体取决于T1.如果T1是左值引用,则后者仅是左值表达式.并且T1只有在外部的第一个参数是左值表达式的情况下,才推导出它是左值引用.
sbi*_*sbi 11
如果我们将t1和t2作为左值,那将如何影响被调用的函数内部?
如果在实例化之后,T1是类型char,并且T2是类,则要传递t1每个副本和t2每个const引用.好吧,除非inner()按照非const引用的方式使用它们,也就是说,在这种情况下你也想这样做.
尝试编写一组outer()函数,在没有右值引用的情况下实现它,推导出从inner()类型传递参数的正确方法.我认为你需要2 ^ 2个东西,相当大的模板元素来推断论证,并且有很多时间来适应所有情况.
然后有人带着一个inner()每个指针接受一个参数.我认为现在是3 ^ 2.(或者4 ^ 2.太好了,我不能费心去思考const指针是否有所作为.)
然后想象你想要为五个参数做这个.或七.
现在你知道为什么一些聪明的头脑想出了"完美的转发":它让编译器为你做这一切.
还没有说清楚的一点是static_cast<T&&>处理const T&得当。
程序:
#include <iostream>
using namespace std;
void g(const int&)
{
cout << "const int&\n";
}
void g(int&)
{
cout << "int&\n";
}
void g(int&&)
{
cout << "int&&\n";
}
template <typename T>
void f(T&& a)
{
g(static_cast<T&&>(a));
}
int main()
{
cout << "f(1)\n";
f(1);
int a = 2;
cout << "f(a)\n";
f(a);
const int b = 3;
cout << "f(const b)\n";
f(b);
cout << "f(a * b)\n";
f(a * b);
}
Run Code Online (Sandbox Code Playgroud)
产生:
f(1)
int&&
f(a)
int&
f(const b)
const int&
f(a * b)
int&&
Run Code Online (Sandbox Code Playgroud)
请注意,'f' 必须是模板函数。如果它只是定义为 'void f(int&& a)' 这不起作用。
小智 5
可能值得强调的是,转发必须与具有转发/通用引用的外部方法一起使用。允许单独使用前向作为以下语句,但除了造成混乱之外没有任何好处。标准委员会可能想要禁用这种灵活性,否则为什么我们不直接使用 static_cast 呢?
std::forward<int>(1);
std::forward<std::string>("Hello");
Run Code Online (Sandbox Code Playgroud)
在我看来,移动和前进是设计模式,是引入 r 值引用类型后的自然结果。我们不应该假设一个方法被正确使用来命名它,除非禁止错误的使用。
| 归档时间: |
|
| 查看次数: |
61664 次 |
| 最近记录: |