【问题标题】:How implementing move constructor affects return value optimization?实现移动构造函数如何影响返回值优化?
【发布时间】:2016-10-01 02:47:54
【问题描述】:

考虑以下代码sn-p:

#include <iostream>
#include <string>

class A { 
 public:
    A() { 
        std::cout << "A::A()\n";
    } 

    ~A() { 
        std::cout << "A::~A()\n";
    } 

    A(const A&) = delete;

    A(A&&) { 
        std::cout << "A::A(A&&)\n";
    };

};

A f() { 
    A a;
    return a;
}

int main() { 
    A a = f();
    return 0;
}

它与g++clang++ 编译良好,输出为

A::A()
A::~A()

在这种情况下,RVO 似乎发挥了作用。请注意,没有调用移动构造函数。

但是,如果从上面的代码中删除那个未使用的移动构造函数,那么 sn-p 就会变成这样:

#include <iostream>
#include <string>

class A { 
 public:
    A() { 
        std::cout << "A::A()\n";
    } 

    ~A() { 
        std::cout << "A::~A()\n";
    } 

    A(const A&) = delete;

};

A f() { 
    A a;
    return a;
}

int main() { 
    A a = f();
    return 0;
}

clang++g++ 都拒绝编译,因为 A 类的复制构造函数被标记为已删除,因此似乎没有发生 RVO。

删除未使用的移动构造函数会如何导致这种情况?

【问题讨论】:

  • 对我来说编译得很好。使用Apple LLVM version 7.3.0 (clang-703.0.31)。还有ideone.com/xTpD56
  • 如果声明复制构造函数,则根本不会隐式声明移动构造函数。

标签: c++ c++11 move-semantics rvo


【解决方案1】:

请注意,在copy elision 优化中,复制/移动构造函数仍然必须存在且可访问。并且不能保证在每种情况下都会执行复制省略。

(强调我的)

即使发生复制省略并且复制/移动构造函数是 未调用,它必须存在且可访问(好像没有优化 完全发生了),否则程序是不正确的。

对于您的代码,复制构造函数已被 deleteed,如果您删除移动构造函数的定义,并且由于类 A 具有用户定义的析构函数,它不会被隐式声明,那么两者移动/复制构造函数不存在且无法访问,这就是编译失败的原因。

总之,这里需要复制/移动语法,编译器会检查它。然后编译器将决定是否执行复制省略(例如在调试模式下)。

顺便说一句:您可以使用 -fno-elide-constructors 与 clang 和 gcc 来禁止它。

【讨论】:

  • 我不明白编译器如何在应用 rvo 之前选择使用什么构造函数(复制或移动)从函数返回本地。是这样定义的吗?
  • @Anton Compiler 正在使用最佳匹配。如果该值是一个右值,并且 move ctor 可用,它将被调用。如果它不是右值,或者移动构造函数不可用,则将使用复制 ctor。在这方面,它与常规函数的重载决议没有什么不同。
  • @Anton 是的,哪个将被选中已定义好。对于您的代码,将选择移动 ctor,因为 f() 将返回一个可能绑定到右值引用的临时文件。无论如何,这似乎是另一个问题。
【解决方案2】:

您需要记住,(N)RVO 是一种优化。即使它启动,代码也应该符合标准,即使用复制(或移动)构造函数构造值。即使构造函数最终没有被调用,它也必须是可用的。

有一个提议允许缺少/不可用的构造函数,如果由于优化而不会被调用,但我怀疑它会被实现。

【讨论】:

  • 这是否意味着在第一个 sn-p local a from function f 中被 move-semantics 返回,然后优化器决定使用 rvo 并抛出对 move-constructor 的调用?
  • 很难说在编译器中第一和第二会发生什么 :) 但是代码必须首先正确,然后再优化。
  • 我们换个说法:第一个代码被剪断是正确的,因为 move ctor 已定义且可访问,它允许从 f 返回本地 a
  • @Anton,没错。从本地值(或参数)返回会将它们视为临时值。
【解决方案3】:

如果你有复制构造函数,如果我没记错的话,你也必须有移动构造函数。

【讨论】:

  • 这充其量只是一个评论(而且是不正确的)。无论如何都不是答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多