在阅读当前move constructor中的standard时,我可以看到以下内容:
12.8复制和移动类对象
如果类X的定义没有显式声明一个移动构造函数,那么当且仅当
时,才会隐式声明一个默认值。- X没有用户声明的复制构造函数,
- X没有用户声明的复制赋值运算符,
- X没有用户声明的移动赋值运算符,以及
- X没有用户声明的析构函数。[注意:当未隐式声明或显式提供移动构造函数时,否则将调用移动构造函数的表达式可能会调用复制构造函数。 - 结束说明]
我认为note部分清楚地提到默认移动构造函数的后退将是复制构造函数。为了理解这个概念,我写了一个小程序来理解这个概念。
#include<iostream>
struct handleclass {
public:
handleclass():length{0}, p{nullptr} {}
handleclass(size_t l):length{l},p{new int[length]} { }
~handleclass() { delete[] p; }
size_t length;
int* p;
};
handleclass function(void) {
handleclass x(10);
return x;
}
int main() {
handleclass y;
std::cout<<y.length<<std::endl;
y = function();
std::cout<<y.length<<std::endl;
handleclass a;
handleclass b(10);
a = std::move(b);
return 0;
}
显然此程序不正确并且由于两个对象的资源浅层复制而导致未定义的行为(终止)。但我的重点是理解在程序中生成和使用的默认移动构造函数。我希望这个例子有意义。
在上面的程序中,在应该调用move构造函数的两种情况下,在我看来编译器正在使用默认的复制构造函数。
根据标准中提到的上述规则,我认为我们应该得到编译器错误,因为现在程序显式尝试调用移动构造函数并且用户既没有实现也没有编译生成默认(隐式),因为上述规则不满足?
然而,这是在没有任何警告/错误的情况下编译并成功运行。有人可以解释默认(隐式)移动构造函数的概念吗?或者我错过了什么?。
答案 0 :(得分:2)
你忘了copy elision这意味着y = function();
可能实际上不会调用任何副本或移动构造函数;只是x
的构造函数和赋值运算符。
某些编译器允许您禁用复制省略,如该线程中所述。
我不确定你的意思是“在应该调用移动构造函数的两种情况下”。实际上零调用应该调用移动构造函数(您的对象没有移动构造函数),并且可以调用复制构造函数的一种情况(return
语句)但可能会被省略。
您有两种赋值运算符:y = function();
和a = std::move(b);
。同样,由于您的类没有移动赋值运算符,因此它们将使用复制赋值运算符。
如果您从复制构造函数和移动构造函数中将对象的代码添加到cout
,则可能有助于您进行测试。
答案 1 :(得分:2)
实际上,由于用户声明的析构函数,没有隐式移动构造函数。
但是有一个隐式的复制构造函数和复制赋值运算符;由于历史原因,析构函数不会抑制这些行为,尽管这种行为已被弃用(因为你指出)它通常会给出无效的复制语义。
它们可用于复制 lvalues 和 rvalues ,因此用于函数返回(可能被省略)和测试程序中的赋值。如果你想阻止它,那么你将不得不删除这些功能。
答案 2 :(得分:2)
尚未隐式声明移动构造函数和移动赋值运算符,因为您已明确定义了析构函数。尽管已隐式声明了复制构造函数和复制赋值运算符(尽管不推荐使用此行为)。
如果没有隐式(或显式)声明移动构造函数和移动赋值运算符,它将回退到使用等效副本。
当您尝试调用移动分配时,它将回退到使用复制分配。