预期的移动与副本

时间:2015-08-04 16:09:38

标签: c++11 destructor move copy-constructor

据我所知,移动语义可以使用移动构造器来消除本来可以复制的内容。例如,返回(可能)大型数据结构的函数现在可以按值返回,并且移动构造函数将用于避免复制。

我的问题是:如果可能的话,编译器是否需要才能复制?似乎并非如此。在这种情况下,以下代码不会具有“实现定义”语义吗?

static const int INVALID_HANDLE = 0xFFFFFFFF;

class HandleHolder {
    int m_handle;
public:
    explicit HandleHolder(int handle) : m_handle(handle) {}
    HandleHolder(HandleHolder& hh) {
        m_handle = hh.m_handle;
    }
    HandleHolder(HandleHolder&& hh) : m_handle(INVALID_HANDLE) {
        swap(m_handle, hh.m_handle);
    }
    ~HandleHolder() noexcept {
        if (m_handle != INVALID_HANDLE) {
            destroy_the_handle_object(m_handle);
        }
    }
};

然后我们说出一个函数:

HandleHolder make_hh(int handle) { return HandleHolder(handle); }

调用哪个构造函数?我希望移动构造函数,但我保证移动构造函数?

我知道这是一个愚蠢的例子 - 例如 - 应删除此对象的复制构造函数,因为没有办法安全地使用它,但语义很简单,我不会'认为这样的事情将是实现定义的。

1 个答案:

答案 0 :(得分:1)

是的,当然。没有任何关于它的实现定义。

如果有一个移动构造函数并且可以使用它,并且它是移动构造函数和复制构造函数之间的选择,则将调用移动构造函数。这是一种保证。

  

[C++11: 13.3.3.2/3]: [..] 标准转换序列S1是比标准转换序列S2更好的转换序列,如果:

     

[..]

     
      
  • S1和S2是引用绑定(8.5.3),既不引用没有ref-qualifier声明的非静态成员函数的隐式对象参数,S1将rvalue引用绑定到rvalue,S2绑定左值参考
  •   
     

[..]

我认为你的困惑源于滥用“elide”一词。编译器可能会忽略复制/移动并用 nothingness 替换它们 - 使用就地构造来完全绕过构造函数的调用。复制elision永远不会导致移动,移动elision永远不会导致副本。对象“转移”发生或不发生。

您可以认为您的程序具有“实现定义”语义,因为您不知道在编译程序之前是否会删除副本/移动 ,并且因为允许这样的省略来修改副作用(例如控制台输出)。但我们并不倾向于这样想。

无论如何,这不会影响将要调用哪个复制和移动构造函数。

您的示例存在进一步的缺陷,因为只能调用您的移动构造函数:您的复制构造函数采用ref-to-non - const,它不能通过rvalue初始化程序绑定