据我所知,移动语义可以使用移动构造器来消除本来可以复制的内容。例如,返回(可能)大型数据结构的函数现在可以按值返回,并且移动构造函数将用于避免复制。
我的问题是:如果可能的话,编译器是否需要才能复制?似乎并非如此。在这种情况下,以下代码不会具有“实现定义”语义吗?
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); }
调用哪个构造函数?我希望移动构造函数,但我保证移动构造函数?
我知道这是一个愚蠢的例子 - 例如 - 应删除此对象的复制构造函数,因为没有办法安全地使用它,但语义很简单,我不会'认为这样的事情将是实现定义的。
答案 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初始化程序绑定