【问题标题】:Rvalue reference and auto&& local variables右值引用和 auto&& 局部变量
【发布时间】:2016-03-21 14:32:28
【问题描述】:

将局部变量定义为右值引用或转发(通用)引用有什么意义?据我了解,任何具有名称的变量都是左值,并且会被视为左值。

例子:

Widget&& w1 = getWidget();
auto&& w2 = getWidget();

w1 和 w2 都是左值,如果它们稍后作为参数传递,将被视为左值。他们的 decltype 可能不是,但这有什么区别?为什么有人需要这样定义变量?

【问题讨论】:

  • 如果getWidget() 返回一个您不想立即移出的右值引用怎么办?
  • @Barry 这些定义是否阻止将返回值移动到局部变量?
  • 另外,Widget&& w1 = ...auto&& w2 = ... 的含义也完全不同。后者可以绑定到左值。
  • 它在力学中用于ranged based for loop
  • 一些像std::vector<bool> 这样的迭代器返回代理对象而不是对元素的引用。 auto& 失败,但 auto&& 有效。

标签: c++ c++11


【解决方案1】:

如果你有一个函数返回一个不能移动的临时值。

Foo some_function();

auto&& f = some_function();

这是合法的。 auto f = some_function(); 要么复制(这可能很昂贵),要么编译失败(如果类也无法复制)。

一般来说,auto&& 会根据初始化的内容推导出 r 或左值引用,如果使用临时值初始化,则会延长其生命周期,同时让您可以作为左值访问它。

“just-loop”模式是一个经典的用法:

for( auto&& x : some_range )

在生成的代码中实际上有一个auto&& x = *it;

您不能将非常量左值引用绑定到临时对象,因此您的另一个选择是Widget const&,它不允许您在其生命周期内修改临时对象。

这种技术对于分解复杂的表达式并查看发生了什么也很有用。只要您不使用极其脆弱的表达式模板,您就可以将表达式a+b+c*d 转换为

auto&& c_times_d = d*d;
auto&& b_plus_c_times_d = b + decltype(c_times_d)c_times_d;
auto&& c_plus_b_plus_c_times_d = c + decltype(b_plus_c_times_d)b_plus_c_times_d;

现在您可以访问生命周期已延长的临时对象,并且您可以轻松地单步执行代码,或在复杂表达式的步骤之间引入额外的步骤:这是机械地发生的。

仅当您未能绑定 每个 子表达式时,对脆弱表达式模板的担忧才成立。 (请注意,使用-> 会生成无数您可能不会注意到的子表达式。)

当我想说“我正在按原样存储某个函数的返回值,而不进行复制”时,我使用auto&&,而表达式的类型并不重要。 auto 是我想制作本地副本的时候。

在通用代码中它非常有用。

Foo const& a(Foo*);
Bar a(Bar*);

template<class T>
auto do_stuff( T*ptr ) {
  auto&& x = a(ptr);
}

在这里,如果您传递 Bar* 它会存储临时值,但如果您将 Foo* 传递给 do_stuff 它会存储 const&amp;

它做的最少。

这是一个返回不可移动不可复制对象的函数示例,以及auto&amp;&amp; 如何让您存储它。否则它是无用的,但它显示了它是如何工作的:

struct Foo {
  Foo(&&)=delete;
  Foo(int x) { std::cout << "Foo " << x << " constructed\n";
};
Foo test() {
  return {3};
}

int main() {
  auto&& f = test();
}

【讨论】:

  • 看起来不可移动的类不能从函数中返回,即使它们有一个复制构造函数(尝试过)。
  • @HaithamGad 当然可以,通过直接构建。我将添加一个示例。
  • 有趣!如果将test() 更改为return Foo(3);return 3;,为什么会失败?
  • @HaithamGad 因为那不直接构造返回值。在这两种情况下,它都会在逻辑上创建一个临时的Foo,然后将其复制到返回值中(嗯,移动)。此副本被省略,但它必须是合法的才能发生省略。同时return {3};直接构造返回值。
【解决方案2】:

据我所知,定义本地右值引用并没有真正的,也就是广泛使用的目的,因为它们的性质,不绑定到左值,仅有助于重载和推导,因此将它们定义为参数一个函数。

可以使用它们,将它们绑定到临时值,例如 int &amp;&amp;rref = 5*2; 但由于几乎所有编译器都在优化表达式 int i = 5*2; 在性能或避免复制方面没有真正的需要。

【讨论】:

  • 你是对的,虽然不完全一样,但它们被认为是相同的。我会更正我的答案。
  • @HaithamGad 我相信未来这个词(双关语不是有意的)是一个转发参考。
【解决方案3】:

一个例子可以是一个数组

template<class T, int N> using raw_array = T[N];

然后

auto && nums = raw_array<int,4>{101, 102, 103, 104};

这允许像普通数组一样使用临时数组。

【讨论】:

  • 它与raw_array&lt;int,4&gt; nums{101, 102, 103, 104}; 有何不同,因为该临时数组的寿命与nums 一样长?
  • 只是有些人喜欢用左边的 auto 开始变量。他们发现它更容易阅读。
【解决方案4】:

声明为auto&amp;&amp; 的变量将遵循完美的转发规则。自己试试吧。这甚至是在 c++14 中使用 lambdas 完成完美转发的方式。

const Type& fun1();
Type&& fun2();

auto&& t1 = fun1(); // works
auto&& t2 = fun2(); // works too

【讨论】:

  • 我明白这一点。 t1 或 t2 的处理方式与说 auto t3 = fun3(); 有何不同?之后呢?
  • 是的。它们都是引用,因为 auto 将复制变量。
猜你喜欢
  • 1970-01-01
  • 2020-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-02
相关资源
最近更新 更多