auto &&告诉我们什么?


168

如果您阅读类似的代码

auto&& var = foo();

foo根据type的值返回的任何函数在哪里T?然后var是rvalue类型的左值引用T。但是,这意味着var什么呢?这是否意味着我们被允许窃取的资源var?是否有任何合理的情况下,您应该auto&&unique_ptr<>告诉您拥有独占所有权时一样,告诉读者您的代码?那例如什么T&&时候T是类类型呢?

我只是想了解一下,是否还有auto&&模板编程之外的其他用例?就像Scott Meyers 在本文“ 通用参考”中的示例中所讨论的一样。


1
我一直在想同样的事情。我了解类型推导的工作原理,但是使用时我的代码怎么auto&&?我一直在考虑为什么将基于范围的for循环扩展auto&&为示例,但是还没有解决这个问题。也许谁回答都能解释。
约瑟夫·曼斯菲尔德

1
这合法吗?我的意思是T的实例在foo返回后立即被销毁,将右值引用存储起来听起来像UB到ne。

1
@aleguna这是完全合法的。我不想返回对局部变量的引用或指针,而是一个值。该功能foo例如样子威力:int foo(){return 1;}
MWid 2012年

9
就像在C ++ 98中一样,@ aleguna对临时人员的引用会延长生命周期。
ecatmur

5
@aleguna生命周期扩展仅适用于本地临时对象,不适用于返回引用的函数。请参阅stackoverflow.com/a/2784304/567292
ecatmur

Answers:


232

通过使用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::forwardauto&&参考,以保持一个事实,即它本来无论是一个左或右值,你的代码说:现在,我已经得到了你的对象从任何一个左或右值的表情,我想保留原来为准它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按值返回,读者应该将其读取为:我对从返回的临时值进行了右值引用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_vectorglobal_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&&在代码中任何的autoauto&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
}

2
我们不能简单地说T vec = get_vector<T>();内部函数foo吗?还是我将其简化到荒谬的程度:)
星号

@Asterisk只有在std :: vector <int&>的情况下,才可以将bcoz T vec分配给左值,如果T为std :: vector <int>,那么我们将使用效率低的按值调用
Kapil,2016年

1
auto&给我相同的结果。我正在使用MSVC2015。GCC会产生错误。
谢尔盖·波多布里

在这里,我使用的是MSVC 2015,auto&给出的结果与auto &&相同。
蔡凯

为什么是我?自动&& j = i; 但允许 int && j = i; 不是 ?
SeventhSon84

14

首先,我建议将我的答案作为侧面阅读,以逐步解释通用引用的模板参数推导如何工作。

这是否意味着我们被允许窃取的资源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)上面的功能模板完全相同。如果varT&&,则将返回右值;如果为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})将是一个失败的扣除。


在您的答案的第一部分中:我的意思是,如果foo()返回value-type T,则var(此表达式)将是一个左值,而其类型(此表达式的)将是对T(即T&&)的右值引用。
MWid 2012年

@MWid:有意义,删除了第一部分。
Xeo 2012年

3

考虑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&&可能会删除呼叫者的所有权(如果存在移动控制器等)。


2
我不确定您的第二个示例是否有效。您不需要完善的转发来调用move构造函数吗?

3
错了 在这两种情况下,都会调用复制构造函数,因为refrvref都是左值。如果您需要move构造函数,则必须编写T t(std::move(rvref))
MWid 2012年

在第一个示例中,您的意思是const ref auto const &吗?
PiotrNycz

@aleguna-您和MWid是正确的,谢谢。我已经确定答案了。
无用的2012年

1
@没用你是对的。但这并不能回答我的问题。您auto&&什么时候使用auto&&?如何使用告诉您代码的读者?
MWid 2012年

-3

auto &&语法使用C ++ 11的两个新功能:

  1. auto部分允许编译器根据上下文(在这种情况下为返回值)推断类型。这没有任何参考条件(允许您指定是否要使用TT &或指定T &&推断类型T)。

  2. &&是新的动作语义。支持移动语义的类型实现了一种构造函数T(T && other),该构造函数可以最佳地移动新类型中的内容。这允许对象交换内部表示,而不是执行深层复制。

这使您可以像:

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

所以:

auto var = foo();

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

auto &&var = foo();

将交换向量的内部表示形式(来自的向量foo和来自的空向量var),因此会更快。

这在新的for循环语法中使用:

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

其中for循环将an保留auto &&为返回值,foo并且item是in中每个值的引用foo


这是不正确的。auto&&不会移动任何东西,只会作为参考。它是左值引用还是右值引用取决于用于对其进行初始化的表达式。
约瑟夫·曼斯菲尔德

在两种情况下,由于std::vectorstd::string都是可移动构造的,因此将调用move构造函数。这与的类型无关var
MWid 2012年

1
@MWid:实际上,对复制/移动构造函数的调用也可以与RVO一起完全消除。
Matthieu M.

@MatthieuM。你是对的。但是我认为在上面的示例中,复制cnstructor将永远不会被调用,因为一切都是可移动构造的。
MWid 2012年

1
@MWid:我的意思是,甚至可以省略move构造函数。省略王牌移动(更便宜)。
Matthieu M. 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.