3 回答
TA贡献1812条经验 获得超5个赞
通过使用auto&& var = <initializer>您的意思是:我将接受任何初始化程序,无论它是左值表达式还是右值表达式,我都将保留其constness。通常用于转发(通常使用T&&)。之所以起作用,是因为“通用引用” auto&&或T&&将绑定到任何东西。
你可能会说,好吧,为什么不直接使用const auto&,因为这将也绑定到什么?使用const引用的问题在于const!您以后将无法将其绑定到任何非const引用或调用未标记的任何成员函数const。
例如,假设您要获取一个std::vector,将迭代器带到其第一个元素,然后以某种方式修改该迭代器指向的值:
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
无论初始化表达式如何,此代码都可以正常编译。auto&&失败的替代方式如下:
auto => will copy the vector, but we wanted a reference
auto& => will only bind to modifiable lvalues
const auto& => will bind to anything but make it const, giving us const_iterator
const auto&& => will bind only to rvalues
因此,这auto&&很完美!auto&&在基于范围的for循环中使用此类示例。有关更多详细信息,请参见我的其他问题。
如果再使用std::forward您auto&&参考,以保持一个事实,即它本来无论是一个左或右值,你的代码说:现在,我已经得到了你的对象从任何一个左或右值的表情,我想保留原来为准它valueness因此我可以最有效地使用它-这可能会使它失效。如:
auto&& var = some_expression_that_may_be_rvalue_or_lvalue;
// var was initialized with either an lvalue or rvalue, but var itself
// is an lvalue because named rvalues are lvalues
use_it_elsewhere(std::forward<decltype(var)>(var));
use_it_elsewhere当原始的初始值设定项是可修改的右值时,这可以消除其胆量以提高性能(避免复制)。
这意味着我们是否可以或何时可以从中窃取资源var?好吧,因为auto&&遗嘱会绑定到任何东西,所以我们可能无法尝试自行消除var漏洞-它很可能是左值甚至是const。但是std::forward,我们可以将其用于可能完全破坏其内部的其他功能。一旦这样做,我们应该考虑var处于无效状态。
现在,将其应用于auto&& var = foo();您的问题中给出的的情况,其中foo返回一个T按值。在这种情况下,我们肯定知道的类型var将推导为T&&。由于我们肯定知道它是右值,因此我们不需要std::forward的许可就可以窃取其资源。在这种特定情况下,知道foo按值返回,读者应该将其读取为:我正在对r所返回的临时值进行右值引用foo,因此我可以很高兴地将其移走。
作为附录,我认为值得一提的是,何时出现类似“表达式some_expression_that_may_be_rvalue_or_lvalue可能会更改”的情况。所以这是一个人为的例子:
std::vector<int> global_vec{1, 2, 3, 4};
template <typename T>
T get_vector()
{
return global_vec;
}
template <typename T>
void foo()
{
auto&& vec = get_vector<T>();
auto i = std::begin(vec);
(*i)++;
std::cout << vec[0] << std::endl;
}
这get_vector<T>()是一个可爱的表达式,根据泛型类型,它可以是左值或右值T。我们实质上get_vector通过的模板参数更改的返回类型foo。
当我们调用时foo<std::vector<int>>,get_vector将按global_vec值返回,从而给出一个右值表达式。或者,当我们调用时foo<std::vector<int>&>,get_vector将global_vec通过引用返回,从而产生一个左值表达式。
如果这样做:
foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
正如预期的那样,我们得到以下输出:
2
1
2
2
如果你要改变auto&&在代码中任何的auto,auto&,const auto&,或const auto&&那么我们不会得到我们想要的结果。
根据auto&&引用是用左值表达式还是右值表达式初始化的,更改程序逻辑的另一种方法是使用类型特征:
if (std::is_lvalue_reference<decltype(var)>::value) {
// var was initialised with an lvalue expression
} else if (std::is_rvalue_reference<decltype(var)>::value) {
// var was initialised with an rvalue expression
}
TA贡献1803条经验 获得超6个赞
首先,我建议将我的答案作为侧面阅读,以逐步解释通用引用的模板参数推导如何工作。
这是否意味着我们被允许窃取的资源var?
不必要。如果foo()突然返回参考,或者您更改了通话但忘了更新,该var怎么办?或者,如果您使用的是通用代码,则的返回类型foo()可能会根据您的参数而变化?
考虑auto&&与T&&in 完全相同template<class T> void f(T&& v);,因为它(几乎†)完全一样。当您需要传递或以任何方式使用它们时,如何使用函数中的通用引用?您用于std::forward<T>(v)获取原始值类别。如果在传递给函数之前是左值,则在通过后将保持左值std::forward。如果是右值,它将再次变为右值(请记住,命名的右值引用是左值)。
那么,您如何var以通用方式正确使用?使用std::forward<decltype(var)>(var)。这将与std::forward<T>(v)上面的功能模板中的完全相同。如果var为T&&,则将返回右值;如果为T&,则将返回左值。
因此,在后面的话题:做什么auto&& v = f();,并std::forward<decltype(v)>(v)在代码库告诉我们?他们告诉我们,这v将以最有效的方式获得并传递。但是请记住,在转发了这样的变量之后,可能会将其移出,因此在不重新设置它的情况下进一步使用它是不正确的。
就个人而言,我使用auto&&的通用代码时,我需要一个modifyable变量。完美转发右值正在修改,因为移动操作可能会窃取其胆量。如果我只是想偷懒(即即使我知道它也不要拼写类型名称)并且不需要修改(例如,仅打印范围内的元素时),我会坚持使用auto const&。
† auto是迄今为止不同之处在于auto v = {1,2,3};将v一个std::initializer_list,而f({1,2,3})将是一个失败的扣除。
TA贡献1829条经验 获得超9个赞
考虑T具有移动构造函数的某种类型,并假设
T t( foo() );
使用该move构造函数。
现在,让我们使用一个中间引用来捕获来自的回报foo:
auto const &ref = foo();
这会排除使用move构造函数,因此必须复制返回值而不是移动它(即使我们std::move在此处使用,我们也无法实际通过const ref进行移动)
T t(std::move(ref)); // invokes T::T(T const&)
但是,如果我们使用
auto &&rvref = foo();
// ...
T t(std::move(rvref)); // invokes T::T(T &&)
move构造函数仍然可用。
并解决您的其他问题:
...在任何合理的情况下,您应该使用auto &&&告诉代码的读者...
正如Xeo所说,第一件事本质上是无论X是什么类型,我都尽可能高效地传递X。因此,看到在auto&&内部使用的代码应该传达出它将在适当的情况下在内部使用移动语义。
...就像您返回unique_ptr <>告诉您拥有专有所有权时一样...
当函数模板接受类型为的参数时T&&,就是说它可能会移动您传入的对象unique_ptr。接受T&&可能会删除呼叫者的所有权(如果存在移动控制器等)。
- 3 回答
- 0 关注
- 1288 浏览
添加回答
举报