auto &&告诉我们什么?

如果你读的代码像

auto&& var = foo();

其中foo是通过typesT的值重新调整的任何函数。 那么var是一个左值types的参数为T的左值。 但是这对var意味着什么? 这是否意味着我们被允许偷走var的资源? 有没有什么合理的情况下,当你使用auto&&告诉读者你的代码与你返回一个unique_ptr<>来告诉你拥有独占所有权一样吗? 当T是class级types时,例如T&&怎么办?

我只是想明白,如果有任何其他auto&&用例比那些模板编程中的那些,就像本文在Scott Meyers的Universal References中所讨论的那样。

通过使用auto&& var = <initializer>你会说: 我将接受任何初始值设定项,无论它是左值还是右值expression式,我都将保留它的常量 。 这通常用于转发 (通常使用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)++; 

无论初始化expression式如何,这段代码都能编译得很好。 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来保留它最初是左值还是右值,那么你的代码就会说: 现在我已经从左值或右值expression式获得了对象,我想保留它原来的价值,所以我可以最有效地使用它 – 这可能会使其无效。 如:

 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在原始初始化器是一个可修改的右值的时候为了性能(避免拷贝)而将它的内容use_it_elsewhere

这是什么意思,我们是否可以或何时可以从var窃取资源? 那么因为auto&&会绑定任何东西,所以我们不可能试图自己撕掉var的内核 – 它可能是一个左值,甚至是const。 然而,我们可以std::forwardstd::forward给可能完全破坏其内部的其他function。 只要我们这样做,我们应该认为var处于无效状态。

现在让我们将其应用于auto&& var = foo(); ,如你的问题所给出的,foo按值返回一个T 在这种情况下,我们肯定知道var的types将被推导为T&& 。 由于我们知道这是一个右值,我们不需要std::forward的权限来窃取资源。 在这个特定的情况下, 知道foo是按照价值回报的 ,读者应该把它看作: 我对从foo返回的临时值进行右值引用,所以我可以愉快地从它移出。


作为一个附录,我认为值得一提的是,像some_expression_that_may_be_rvalue_or_lvalue这样的expression式可能会出现,除了“你的代码可能改变”的情况。 所以这是一个人为的例子:

 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>()是一个可爱的expression式,可以是左值或右值,具体取决于genericstypesT 我们实质上通过foo的模板参数get_vector改变get_vector的返回types。

当我们调用foo<std::vector<int>>get_vector将通过值返回global_vec ,它给出一个右值expression式。 或者,当我们调用foo<std::vector<int>&>get_vector将通过引用返回global_vec ,产生一个左值expression式。

如果我们这样做:

 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&&更改为autoauto&const auto& ,或者const auto&&那么我们就不会得到我们想要的结果。


根据auto&&引用是用左值还是右值expression式初始化来改变程序逻辑的另一种方法是使用types特征:

 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 } 

首先,我build议阅读我的这个答案作为一个循序渐进的解释如何通用引用的模板参数推导的解释。

这是否意味着我们被允许偷走var的资源?

不必要。 如果foo()突然返回一个引用,或者你改变了调用,但忘记更新使用var ? 或者,如果你在generics代码和返回types的foo()可能会改变取决于你的参数?

想想auto&&template<class T> void f(T&& v);T&&完全一样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将以最有效的方式获得并传递。 请记住,在转发这样一个variables之后,它可能会被移出来,所以如果不重新使用它,它会被错误地使用。

就我个人而言,当我需要一个可修改variables时,我在generics代码中使用auto&& 。 完美转发右值正在修改,因为移动操作可能会窃取它的胆量。 如果我只是想懒惰(即,即使我知道它不拼写types的名称),而不需要修改(例如,当打印范围的元素时),我会坚持auto const&


auto与目前的不同在于auto v = {1,2,3}; 会使v成为一个std::initializer_list ,而f({1,2,3})将是一个扣除失败。

考虑一些具有移动构造函数的typesT ,并假设

 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所说的,第一件事就是我尽可能高效地传递X ,无论typesX是什么。 因此,看到内部使用auto&&代码应该通知它将在适当的时候在内部使用移动语义。

…就像你当你返回一个unique_ptr <>来告诉你拥有独占所有权…

当一个函数模板接受一个types为T&&的参数时,就是说它可能会移动你传入的对象。返回unique_ptr明确地赋予调用者所有权; 接受T&&可能会删除主叫方的所有权(如果存在移动等)。

auto &&语法使用了C ++ 11的两个新特性:

  1. auto部分让编译器根据上下文(这里是返回值)推导出types。 这是没有任何参考资格(允许您指定是否要TT &T &&为推断typesT )。

  2. &&是新的移动语义。 支持移动语义的types实现了一个构造函数T(T && other) ,以最佳方式移动新types的内容。 这允许对象交换内部表示而不是执行深度复制。

这可以让你有这样的东西:

 std::vector<std::string> foo(); 

所以:

 auto var = foo(); 

将执行返回的vector(昂贵)的副本,但是:

 auto &&var = foo(); 

将交换vector的内部表示(来自foo的vector和来自var的空vector),所以会更快。

这在新的for-loop语法中使用:

 for (auto &item : foo()) std::cout << item << std::endl; 

for循环持有一个auto &&来自foo的返回值, item是对foo每个值的引用。