auto&& 告诉我们什么?
What does auto&& tell us?
如果您读取
的代码 auto&& var = foo();
其中 foo
是任何函数,按 T
类型的值返回。然后,var
是类型RVALUE引用T
的LVALUE。但是这对var
意味着什么?这是否意味着,我们被允许窃取var
的资源?当您应该使用auto&&
告诉读者时,当您返回unique_ptr<>
以告诉您拥有独家所有权时,是否有任何合理的情况告诉读者?例如, T&&
当T
是类类型时呢?
我只想了解auto&&
的其他用例,而不是模板编程中的用例;就像Scott Meyers的示例中所讨论的示例中所讨论的那样。
通过使用auto&& var = <initializer>
,您是在说:我将接受任何初始化器无论它是LVALUE还是RVALUE表达式,我都将保留其constness 。这通常用于转发(通常使用T&&
)。此原因是因为"通用引用" auto&&
或T&&
将绑定到 nything 。
您可能会说,那么为什么不使用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
循环。有关更多详细信息,请参见我的其他问题。
如果然后在auto&&
上使用std::forward
,以保留它最初是LVALUE或RVALUE的事实,您的代码说:现在,我已经从lvalue或rvalue表达式中获得了您的对象,我想保留它最初拥有的任何珍贵性,以便我可以最有效地使用它 - 这可能使它无效。如:
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
为了性能(避免副本)撕裂其肠道,当原始初始化器是可修改的rvalue时。
这意味着我们是否可以或何时可以从var
窃取资源?好吧,由于auto&&
将绑定到任何东西,因此我们不可能尝试撕开var
S胆量 - 它很可能是一个lvalue甚至const。但是,我们可以 std::forward
它可以完全破坏其内部的其他功能。一旦我们这样做,我们应该考虑var
处于无效状态。
现在,让我们将其应用于您的问题中给出的auto&& var = foo();
的情况,其中FOO返回T
逐值。在这种情况下,我们确定var
的类型将被推论为T&&
。由于我们可以肯定地知道这是一个rvalue,因此我们不需要std::forward
的许可来窃取其资源。在这种特定情况下,知道foo
按值返回,读者应该将其读为:我正在对foo
返回的临时级进行rvalue引用,因此我可以很高兴地从它。
作为附录,我认为值得一提的是,除了"好吧,您的代码可能会改变"情况外,诸如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
而成为lvalue或rvalue。我们本质上通过foo
的模板参数更改get_vector
的返回类型。
当我们调用foo<std::vector<int>>
时,get_vector
将按值返回global_vec
,该值提供了RVALUE表达式。另外,当我们致电foo<std::vector<int>&>
时,get_vector
将通过引用返回global_vec
,导致LVALUE表达式。
如果我们这样做:
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&&
是否使用lvalue或rvalue表达式来更改程序逻辑的替代方法是使用类型特征:
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
}
首先,我建议阅读我的答案,作为逐步解释的侧阅读,以解释模板参数如何扣除通用参考的扣除。
这是否意味着我们被允许窃取
var
的资源?
不一定。如果foo()
突然返回了参考,或者您更改了呼叫,但忘了更新var
的使用?或者,如果您使用通用代码,并且foo()
的返回类型可能会根据您的参数而更改?
认为auto&&
与template<class T> void f(T&& v);
中的T&&
完全相同,因为它(几乎†)完全相同。当您需要以任何方式将它们传递或使用它们时,您如何处理函数中的通用引用?您使用std::forward<T>(v)
将原始值类别返回。如果在传递到您的功能之前是LVALUE,则在通过std::forward
之后会保持LVALUE。如果是一个rvalue,它将再次成为一个rvalue(请记住,命名的rvalue参考是一个lvalue)。
那么,您如何以通用方式正确使用var
?使用std::forward<decltype(var)>(var)
。这将与上面函数模板中的std::forward<T>(v)
完全相同。如果var
是T&&
,您将获得一个rvalue,如果是T&
,则您将获得一个lvalue。
那么,回到主题:代码库中的auto&& v = f();
和std::forward<decltype(v)>(v)
告诉我们什么?他们告诉我们,v
将以最有效的方式获取和传递。请记住,在转发了这样的变量之后,它可能是从中移动的,所以它是不正确的进一步使用它而无需重置。
个人,当我需要一个修改变量时,我在通用代码中使用auto&&
。完美的rvalue正在修改,因为该动作操作可能会窃取其胆量。如果我只想变得懒惰(即,即使我知道即使不拼写类型名称),并且不需要修改(例如,仅在范围的打印元素时),我会坚持使用auto const&
。
†auto
在远方不同,以至于auto v = {1,2,3};
将使v
成为std::initializer_list
,而f({1,2,3})
将是扣除失败。
考虑某种类型的 T
具有移动构造函数,并假设
T t( foo() );
使用该移动构造函数。
现在,让我们使用中间参考来捕获foo
的返回:
auto const &ref = foo();
此排除了移动构造函数的使用,因此必须复制返回值而不是移动(即使我们在此处使用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 &&)
移动构造函数仍然可用。
并解决您的其他问题:
正如Xeo所说,...当您使用自动&amp;&amp;告诉读者您的代码...
第一件事本质上是我尽可能高效地传递X,无论X类型是什么。因此,在内部看到使用auto&&
的代码,应该在适当的情况下传达其内部使用移动语义的代码。
...就像您返回suolty_ptr&lt;>时所做的那样,告诉您拥有独家所有权...
当功能模板采用类型T&&
的参数时,它可能会移动您传递的对象。返回unique_ptr
明确向呼叫者提供所有权;接受 T&&
may 从呼叫者中删除所有权(如果存在ctor等等,等等)。
auto &&
使用两个语法使用两个C 的新功能11:
-
auto
零件使编译器可以根据上下文推导类型(在这种情况下为返回值)。这是没有任何参考资格的(允许您指定是否要T
,T &
或T &&
进行推论类型T
)。 -
&&
是新的移动语义。支持移动语义的类型实现了构造函数T(T && other)
,该构造函数可最佳地移动新类型中的内容。这允许对象交换内部表示,而不是执行深层副本。
这使您可以拥有类似的东西:
std::vector<std::string> foo();
so:
auto var = foo();
将执行返回矢量的副本(昂贵),但是:
auto &&var = foo();
将交换向量的内部表示(来自foo
的向量和var
的空矢量),因此会更快。
这是在新的循环语法中使用的:
for (auto &item : foo())
std::cout << item << std::endl;
for-loop持有auto &&
到foo
的返回值,而item
是foo
中的每个值的引用。
- 当我们从/tp地址中添加/减去一个整数时会发生什么
- 当我们为(;;) 写作时,它做了什么?for 循环中的双分号有什么作用?
- int数据类型的指针指向的是什么,如果是一个类的私有数据成员,我们创建了该类的两个对象?
- 当我们进行一些操作时,应该使用什么'std::string'或'std::stringstream'?
- 哪种方式更快?究竟发生了什么,我们没有看到什么?
- C++不重载时间函数,所以我们不需要写 NULL 有什么原因吗?
- 如果我们不从 std::qsort 返回 0 会发生什么?
- 如果真的需要std::move,我们应该什么时候声明右值refs
- 当我们声明 cin 为 int 并从 cin 中获取输入并在 cout 中打印 cin 时会发生什么?
- 当我们已经有 char[] 时,在 c++ 中字符串的必要性是什么?
- 在这种情况下,我们可以使用静态而不是朋友吗,还有其他解决方案是什么
- 这段代码中的arr[s[i]-'a']有什么用,为什么我们用"a"减去所有字符?
- 究竟发生了什么,我们需要在 c++ 中双重调度/访客
- 当我们push_back元素时,std::vector 什么时候会放大自己?
- 当我们在 C++ 中说"initialize the object"时,它实际上意味着什么?
- 为什么 c++ 中需要条件来实现良好的编码实践,即使我们没有什么可写的
- 如果我们在 C++ 中声明一个起始'&'的 char 变量会发生什么?
- 当我们在MSDN中调用UpdateWindows()时,该方法是什么
- 以下错误的原因是什么?我们在编译 Thrift C++ 代码时收到此错误
- 此代码的复杂性是什么?我们是否应该总结复杂性