【问题标题】:Lifetime of rvalue ref右值引用的生命周期
【发布时间】:2015-11-18 13:56:26
【问题描述】:

下面的代码工作正常,据我所知,每次调用函数时,都会创建一个局部变量(即向量),并且所有权将在第一次调用时在右值引用和 const 引用中转移(如果我删除它甚至不会编译)在第二次调用。结果,当函数终止时,局部变量实际上并没有死,而是当 main 中的引用超出范围时(即在main() 的末尾),我认为

#include <iostream>
#include <vector>

std::vector<int> get_v(void)
{
    std::vector<int> v{1,2,3};
    return v;
}

int main() {
    std::vector<int> &&rval_ref = get_v();

    for(unsigned int i = 0; i < rval_ref.size(); ++i)
        std::cout << rval_ref[i] << "\n";

    const std::vector<int> &con_ref = get_v();

    for(unsigned int i = 0; i < con_ref.size(); ++i)
        std::cout << con_ref[i] << "\n";

    return 0;
}

输出:

gsamaras@pythagoras:~$ g++ -std=c++0x -Wall px.cpp
gsamaras@pythagoras:~$ ./a.out
1
2
3
1
2
3

但我认为局部变量在超出范围时会死掉,除非在它们之前有一个 static 关键字,或者它们已经被动态分配,甚至被复制。在这种情况下,向量不会被复制。也许我的 C 背景让我无法理解这里的概念。你能帮帮我吗?

作为旁注,第一种情况允许您修改向量,而第二种显然不会。猜猜第一个是 C++11 的特性,而第二个是传统的。


我刚刚用自定义类做了一个示例,不会调用复制构造函数,但它会像上面的示例一样工作!

【问题讨论】:

  • get_vvalue 而非 reference 返回。
  • 那么,复制构造函数将被称为@CaptainObvlious?朋友告诉我不会!
  • 副本可能会被忽略,也可能不会。取决于优化。无论哪种方式,您仍然按值返回,因此将创建属于调用者的向量的唯一实例。
  • @CaptainObvlious 这对我来说并不明显,抱歉!也检查我的编辑。我不明白这是怎么发生的。
  • 您返回一个副本,该副本成为 临时(未命名)对象。你的const&amp; 然后绑定到未命名的临时给它一个名字(从而延长它的生命)。

标签: c++ c++11 scope rvalue-reference rvalue


【解决方案1】:

您的get_v 函数返回一个值,因此v 超出范围并不重要,因为您返回了它的

和以下没有什么不同:

int a()
{
    int j = 3;
    return j; // returns the *value* of j
}

C++ 标准允许编译器通过以某种方式将v 转发给调用者来省略复制构造函数。如果引用绑定到临时对象,C++ 标准还要求延长临时对象的生命周期。

【讨论】:

  • 哦,转发是这里的关键。您能否对此进行一些扩展(我非常感谢您在这里提供一些简单的话)或提供解释性链接?
  • 参见维基百科上的return value optimizationcopy elision
  • 谢谢大卫。从那里的示例中,我了解到转发与 RVO 有关(是同一件事吗?我想不是……)。因此,在这种情况下,编译器足够聪明,不会破坏局部变量,而是“重命名”它,方法是在 main()? 中给它变量的名称?我不知道你为什么将我链接到副本 ε。
  • @gsamaras 对。看来你明白了。是的,指向 RVO 的链接是错误的。
  • @gsamaras 复制省略 是标准在提及省略复制和移动时使用的内容,RVO 是一种复制省略的非正式名称。复制省略也可能发生在投掷和接球以及复制未绑定的临时对象时(这通常发生在 RVO 之后)。
【解决方案2】:

当您编写std::vector&lt;int&gt; &amp;&amp;rval_ref = get_v(); 时,这些是到达return v; 时的概念步骤

  1. 创建了一个名为返回值的临时对象。这个对象被初始化为std::vector&lt;int&gt; x{v};。它“存在”在main 中,并且当main 中的此语句完成时自然会“超出范围”。
  2. v 被销毁
  3. 引用rval_ref 绑定到该临时对象。将引用绑定到临时对象会导致对象的生命周期延长以匹配引用的生命周期。

“临时对象”这个名称有点用词不当,因为该对象实际上可能会持续很长时间,但这是官方名称。 “未命名的对象”是另一种可能的描述。

您的引用不是引用v,而是引用它的副本(因为您的函数按值返回)。所以即使v被销毁,副本也不会被销毁。

由于复制省略,您的测试代码没有显示复制构造函数调用(上面的第 1 步)。复制省略意味着编译器可以选择为v 使用与返回值相同的内存空间;并省略 v 的析构函数和返回值的复制构造函数(即使这些函数有副作用)。

【讨论】:

    猜你喜欢
    • 2019-12-18
    • 2015-10-02
    • 1970-01-01
    • 2012-11-15
    • 1970-01-01
    • 2016-04-11
    • 1970-01-01
    • 2018-01-01
    相关资源
    最近更新 更多