C++ auto&& 告诉我们什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13230480/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What does auto&& tell us?
提问by MWid
If you read code like
如果你阅读像这样的代码
auto&& var = foo();
auto&& var = foo();
where foo
is any function returning by value of type T
. Then var
is an lvalue of type rvalue reference to T
. But what does this imply for var
? Does it mean, we are allowed to steal the resources of var
? Are there any reasonable situations when you should use auto&&
to tell the reader of your code something like you do when you return a unique_ptr<>
to tell that you have exclusive ownership? And what about for example T&&
when T
is of class type?
哪里foo
是按类型值返回的任何函数T
。然后var
是对 的右值引用类型的左值T
。但这意味着var
什么?这是否意味着,我们可以窃取资源var
?是否有任何合理的情况,您应该使用auto&&
返回 aunique_ptr<>
来告诉您的代码的读者您拥有独占所有权时所做的事情?例如什么T&&
时候T
是类类型呢?
I just want to understand, if there are any other use cases of auto&&
than those in template programming; like the ones discussed in the examples in this article Universal Referencesby Scott Meyers.
我只是想了解一下,是否还有其他用例而auto&&
不是模板编程中的用例;就像Scott Meyers在这篇文章Universal References中的例子中讨论的那样。
回答by Joseph Mansfield
By using auto&& var = <initializer>
you are saying: I will accept any initializer regardless of whether it is an lvalue or rvalue expression and I will preserve its constness. This is typically used for forwarding(usually with T&&
). The reason this works is because a "universal reference", auto&&
or T&&
, will bind to anything.
通过使用auto&& var = <initializer>
您是在说:我将接受任何初始化程序,无论它是左值还是右值表达式,并且我将保留其 constness。这通常用于转发(通常使用T&&
)。这样做的原因是因为“通用引用”auto&&
或T&&
将绑定到任何东西。
You might say, well why not just use a const auto&
because that will alsobind to anything? The problem with using a const
reference is that it's const
! You won't be able to later bind it to any non-const references or invoke any member functions that are not marked const
.
你可能会说,为什么不直接使用 aconst auto&
因为它也可以绑定到任何东西呢?使用const
引用的问题在于它是const
! 您以后将无法将其绑定到任何非常量引用或调用任何未标记为 的成员函数const
。
As an example, imagine that you want to get a std::vector
, take an iterator to its first element and modify the value pointed to by that iterator in some way:
例如,假设您想要获取 a std::vector
,将迭代器带到其第一个元素并以某种方式修改该迭代器指向的值:
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
This code will compile just fine regardless of the initializer expression. The alternatives to auto&&
fail in the following ways:
无论初始化表达式如何,这段代码都可以很好地编译。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
So for this, auto&&
works perfectly! An example of using auto&&
like this is in a range-based for
loop. See my other questionfor more details.
因此,为此,auto&&
完美运行!auto&&
像这样使用的一个例子是在基于范围的for
循环中。有关更多详细信息,请参阅我的另一个问题。
If you then use std::forward
on your auto&&
reference to preserve the fact that it was originally either an lvalue or an rvalue, your code says: Now that I've got your object from either an lvalue or rvalue expression, I want to preserve whichever valueness it originally had so I can use it most efficiently - this might invalidate it.As in:
如果您然后std::forward
在您的auto&&
引用上使用它来保留它最初是左值或右值的事实,您的代码会说:既然我已经从左值或右值表达式中获得了您的对象,我想保留它最初的任何价值有这样我可以最有效地使用它 - 这可能会使它无效。如:
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));
This allows use_it_elsewhere
to rip its guts out for the sake of performance (avoiding copies) when the original initializer was a modifiable rvalue.
use_it_elsewhere
当原始初始值设定项是可修改的右值时,这允许为了性能(避免副本)而撕掉它的内脏。
What does this mean as to whether we can or when we can steal resources from var
? Well since the auto&&
will bind to anything, we cannot possibly try to rip out var
s guts ourselves - it may very well be an lvalue or even const. We can however std::forward
it to other functions that may totally ravage its insides. As soon as we do this, we should consider var
to be in an invalid state.
这对于我们是否可以或何时可以窃取资源意味着什么var
?好吧,既然auto&&
will 绑定到任何东西,我们不可能尝试自己撕掉var
s 的内脏——它很可能是一个左值甚至是 const。然而std::forward
,我们可以将其用于可能完全破坏其内部的其他功能。一旦我们这样做,我们就应该考虑var
处于无效状态。
Now let's apply this to the case of auto&& var = foo();
, as given in your question, where foo returns a T
by value. In this case we know for sure that the type of var
will be deduced as T&&
. Since we know for certain that it's an rvalue, we don't need std::forward
's permission to steal its resources. In this specific case, knowing that foo
returns by value, the reader should just read it as: I'm taking an rvalue reference to the temporary returned from foo
, so I can happily move from it.
现在让我们将其应用于 的情况auto&& var = foo();
,如您的问题所示,其中 foo 返回一个T
by 值。在这种情况下,我们肯定知道 的类型var
将被推导出为T&&
。因为我们确定它是一个右值,所以我们不需要std::forward
的许可来窃取它的资源。在这种特定情况下,知道foo
按值返回,读者应该将其读为:我正在获取对从 返回的临时值的右值引用foo
,因此我可以愉快地离开它。
As an addendum, I think it's worth mentioning when an expression like some_expression_that_may_be_rvalue_or_lvalue
might turn up, other than a "well your code might change" situation. So here's a contrived example:
作为附录,我认为值得一提的是何时some_expression_that_may_be_rvalue_or_lvalue
可能会出现like 表达式,而不是“你的代码可能会改变”的情况。所以这是一个人为的例子:
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;
}
Here, get_vector<T>()
is that lovely expression that could be either an lvalue or rvalue depending on the generic type T
. We essentially change the return type of get_vector
through the template parameter of foo
.
这get_vector<T>()
是一个可爱的表达式,它可以是左值或右值,具体取决于泛型类型T
。我们本质上是get_vector
通过 的模板参数来改变 的返回类型foo
。
When we call foo<std::vector<int>>
, get_vector
will return global_vec
by value, which gives an rvalue expression. Alternatively, when we call foo<std::vector<int>&>
, get_vector
will return global_vec
by reference, resulting in an lvalue expression.
当我们调用时foo<std::vector<int>>
,get_vector
将按global_vec
值返回,它给出了一个右值表达式。或者,当我们调用 时foo<std::vector<int>&>
,get_vector
将global_vec
通过引用返回,从而产生一个左值表达式。
If we do:
如果我们这样做:
foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
We get the following output, as expected:
正如预期的那样,我们得到以下输出:
2
1
2
2
If you were to change the auto&&
in the code to any of auto
, auto&
, const auto&
, or const auto&&
then we won't get the result we want.
如果你要改变auto&&
在代码中任何的auto
,auto&
,const auto&
,或const auto&&
那么我们不会得到我们想要的结果。
An alternative way to change program logic based on whether your auto&&
reference is initialised with an lvalue or rvalue expression is to use type traits:
根据您的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
}
回答by Xeo
First, I recommend reading this answer of mineas a side-read for a step-by-step explanation on how template argument deduction for universal references works.
首先,我建议阅读我的这个答案作为旁读,以逐步解释通用引用的模板参数推导如何工作。
Does it mean, we are allowed to steal the resources of
var
?
这是否意味着,我们可以窃取资源
var
?
Not necessarily. What if foo()
all of a sudden returned a reference, or you changed the call but forgot to update the use of var
? Or if you're in generic code and the return type of foo()
might change depending on your parameters?
不必要。如果foo()
突然返回一个引用,或者您更改了调用但忘记更新使用,该var
怎么办?或者,如果您使用通用代码并且返回类型foo()
可能会根据您的参数而改变?
Think of auto&&
to be exactly the same as the T&&
in template<class T> void f(T&& v);
, because it's (nearly?) exactly that. What do you do with universal references in functions, when you need to pass them along or use them in any way? You use std::forward<T>(v)
to get the original value category back. If it was an lvalue before being passed to your function, it stays an lvalue after being passed through std::forward
. If it was an rvalue, it will become an rvalue again (remember, a named rvalue reference is an lvalue).
认为auto&&
与T&&
in完全相同template<class T> void f(T&& v);
,因为它(几乎?)正是如此。当您需要传递它们或以任何方式使用它们时,您如何处理函数中的通用引用?您std::forward<T>(v)
用来获取原始值类别。如果它在传递给您的函数之前是左值,则在传递之后它保持左值std::forward
。如果它是一个右值,它将再次成为一个右值(记住,一个命名的右值引用是一个左值)。
So, how do you use var
correctly in a generic fashion? Use std::forward<decltype(var)>(var)
. This will work exactly the same as the std::forward<T>(v)
in the function template above. If var
is a T&&
, you'll get an rvalue back, and if it is T&
, you'll get an lvalue back.
那么,您如何var
以通用方式正确使用?使用std::forward<decltype(var)>(var)
. 这将与std::forward<T>(v)
上面的函数模板中的完全相同。如果var
是 a T&&
,你会得到一个右值,如果是T&
,你会得到一个左值。
So, back on topic: What do auto&& v = f();
and std::forward<decltype(v)>(v)
in a codebase tell us? They tell us that v
will be acquired and passed on in the most efficient way.Remember, though, that after having forwarded such a variable, it's possible that it's moved-from, so it'd be incorrect use it further without resetting it.
因此,在后面的话题:做什么auto&& v = f();
,并std::forward<decltype(v)>(v)
在代码库告诉我们?他们告诉我们,v
它将以最有效的方式获得和传递。但是请记住,在转发了这样的变量之后,它可能会被移出,因此在不重置它的情况下进一步使用它是不正确的。
Personally, I use auto&&
in generic code when I need a modifyablevariable. Perfect-forwarding an rvalue is modifying, since the move operation potentially steals its guts. If I just want to be lazy (i.e., not spell the type name even if I know it) and don't need to modify (e.g., when just printing elements of a range), I'll stick to auto const&
.
就个人而言,我使用auto&&
的通用代码时,我需要一个modifyable变量。完美转发右值正在修改,因为移动操作可能会窃取它的胆量。如果我只是想偷懒(即,即使我知道类型名称也不拼写)并且不需要修改(例如,当只是打印范围的元素时),我会坚持使用auto const&
.
? auto
is in so far different that auto v = {1,2,3};
will make v
an std::initializer_list
, whilst f({1,2,3})
will be a deduction failure.
? auto
是迄今为止不同之处在于auto v = {1,2,3};
将v
一个std::initializer_list
,而f({1,2,3})
将是一个失败的扣除。
回答by Useless
Consider some type T
which has a move constructor, and assume
考虑一些T
具有移动构造函数的类型,并假设
T t( foo() );
uses that move constructor.
使用那个移动构造函数。
Now, let's use an intermediate reference to capture the return from foo
:
现在,让我们使用中间引用来捕获来自 的返回foo
:
auto const &ref = foo();
this rules out use of the move constructor, so the return value will have to be copied instead of moved (even if we use std::move
here, we can't actually move through a const ref)
这排除了移动构造函数的使用,因此返回值必须被复制而不是移动(即使我们std::move
在这里使用,我们实际上也不能通过 const ref 移动)
T t(std::move(ref)); // invokes T::T(T const&)
However, if we use
但是,如果我们使用
auto &&rvref = foo();
// ...
T t(std::move(rvref)); // invokes T::T(T &&)
the move constructor is still available.
移动构造函数仍然可用。
And to address your other questions:
并解决您的其他问题:
... Are there any reasonable situations when you should use auto&& to tell the reader of your code something ...
... 当您应该使用 auto&& 告诉代码的读者某些事情时,是否有任何合理的情况...
The first thing, as Xeo says, is essentially I'm passing X as efficiently as possible, whatever type X is. So, seeing code which uses auto&&
internally should communicate that it will use move semantics internally where appropriate.
正如 Xeo 所说,第一件事本质上是我尽可能有效地传递 X,无论 X 是什么类型。因此,查看auto&&
内部使用的代码应该表明它将在适当的情况下在内部使用移动语义。
... like you do when you return a unique_ptr<> to tell that you have exclusive ownership ...
...就像当你返回一个 unique_ptr<> 来告诉你拥有独占所有权时所做的那样......
When a function template takes an argument of type T&&
, it's saying it may move the object you pass in. Returning unique_ptr
explicitly gives ownership to the caller; accepting T&&
may removeownership from the caller (if a move ctor exists, etc.).
当函数模板采用 type 参数时T&&
,它表示它可能会移动您传入的对象。unique_ptr
显式返回将所有权授予调用者;接受T&&
可能会从调用者那里删除所有权(如果存在移动构造函数等)。
回答by reece
The auto &&
syntax uses two new features of C++11:
该auto &&
语法使用C ++ 11的两个新功能:
The
auto
part lets the compiler deduce the type based on the context (the return value in this case). This is without any reference qualifications (allowing you to specify whether you wantT
,T &
orT &&
for a deduced typeT
).The
&&
is the new move semantics. A type supporting move semantics implements a constructorT(T && other)
that optimally moves the content in the new type. This allows an object to swap the internal representation instead of performing a deep copy.
该
auto
部分让编译器根据上下文(在本例中为返回值)推断类型。这是一个没有任何参考的资格(允许你指定是否想要T
,T &
或T &&
进行推断的类型T
)。这
&&
是新的移动语义。支持移动语义的类型实现了一个构造函数T(T && other)
,该构造函数以最佳方式移动新类型中的内容。这允许对象交换内部表示而不是执行深度复制。
This allows you to have something like:
这允许您拥有以下内容:
std::vector<std::string> foo();
So:
所以:
auto var = foo();
will perform a copy of the returned vector (expensive), but:
将执行返回向量的副本(昂贵),但是:
auto &&var = foo();
will swap the internal representation of the vector (the vector from foo
and the empty vector from var
), so will be faster.
将交换向量的内部表示(向量 fromfoo
和空向量 from var
),所以会更快。
This is used in the new for-loop syntax:
这用于新的 for 循环语法:
for (auto &item : foo())
std::cout << item << std::endl;
Where the for-loop is holding an auto &&
to the return value from foo
and item
is a reference to each value in foo
.
其中 for 循环保存auto &&
来自 的返回值,foo
并且item
是对 中每个值的引用foo
。