为什么在带有用户定义析构函数的类中得到隐式move构造函数?



我对隐式move构造函数的某些方面感到困惑。

我的理解是,如果没有用户声明的复制构造函数、复制赋值操作符、移动赋值操作符和析构函数,则编译器会为该类提供隐式声明的move构造函数。

在我的例子中'Heavy'就是这种情况。它的行为符合预期(数据被移动)。

'HeavyWithDestructor'不符合隐式声明的move构造函数,因为它有一个析构函数,但是我可以&;std::move&;它。在某种程度上,它是一个副本(参见数据指针)。

在我看来,这看起来像一个平凡的移动构造函数,因为它执行的操作与平凡的复制构造函数相同(就像通过std::memmove)。

但是如果我一开始就不具备创建隐式移动构造函数的条件,它怎么可能是一个平凡的移动构造函数。此外,'std::is_trivially_move_constructible_v'表明这不是一个平凡的移动构造函数。

#include <iostream>
#include <vector>
#include <type_traits>

using namespace std;

constexpr int largeNumber = 10000000;
#define OUT(...) std::cout << #__VA_ARGS__ << " : " << __VA_ARGS__ << 'n'

// Consistent with an implicit 'move' constructor.
class Heavy
{
vector<int> v_;
public:
Heavy() : v_(vector<int>(largeNumber)) {}

int* getDatap() { return v_.data(); }
};

// Not consistent with an implicit 'move' constructor. (Because has a destructor)
class HeavyWithDestructor
{
vector<int> v_;
public:
HeavyWithDestructor() : v_(vector<int>(largeNumber)) {}
~HeavyWithDestructor(){}

int* getDatap() { return v_.data(); }
};

int main()
{
cout << "Moving a heavy object" << endl;

OUT(std::is_move_constructible_v<Heavy>);
OUT(std::is_trivially_move_constructible_v<Heavy>);

Heavy originalHeavy;
cout << "Data* in original() -> " << originalHeavy.getDatap() << endl;
Heavy finalHeavy = move(originalHeavy);
cout << "Data* in main()     -> " << finalHeavy.getDatap() << endl << endl;


cout << "Moving a heavy object with a destructor" << endl;

OUT(std::is_move_constructible_v<HeavyWithDestructor>);
OUT(std::is_trivially_move_constructible_v<HeavyWithDestructor>);

HeavyWithDestructor originalWoDestructor;
cout << "Data* in original() -> " << originalWoDestructor.getDatap() << endl;
HeavyWithDestructor finalWoDestructor = move(originalWoDestructor);
cout << "Data* in main()     -> " << finalWoDestructor.getDatap() << endl;

return 0;
}

我得到以下输出:我可以确认我正在移动'Heavy',因为指向向量数据的指针指向相同的位置。我还可以确认'HeavyWithDestructor'正在复制,而不是移动数据。

Moving a heavy object
std::is_move_constructible_v<Heavy> : 1
std::is_trivially_move_constructible_v<Heavy> : 0
Data* in original() -> 000001E3FB193080
Data* in main()     -> 000001E3FB193080
Moving a heavy object with a destructor
std::is_move_constructible_v<HeavyWithDestructor> : 1
std::is_trivially_move_constructible_v<HeavyWithDestructor> : 0
Data* in original() -> 000001E3FD7C6080
Data* in main()     -> 000001E38000A080

编译器为'HeavyWithDestructor'声明的构造函数是什么?如果这个构造函数没有移动数据,为什么我仍然可以在它上面使用std::move ?如果我试图通过定义复制构造函数来让编译器不为我声明移动构造函数,那么我就不能使用std::move。我得到编译错误。这是我所期望的。由此,我推断我得到的构造函数不是复制构造函数。我最初怀疑这是一个微不足道的移动构造函数(其行为与std::memmove中的行为一样),但我有迹象表明这也不对。这是什么?

我使用vs2019 c++17作为编译器。

HeavyWithDestructor是典型的c++ 03类型:可复制但不可移动(什么是"可移动"?)因此,为了兼容性,每当尝试移动时都会复制它。这样做的技术原因是const HeavyWithDestructor&可以绑定到右值;道德上的原因是std::move一如既往地授予权限移动某物,但不需要它(或它本身)。

(您对复制构造函数的实验不够详细,无法复制,但可能涉及HeavyWithDestructor(HeavyWithDestructor&),它仍然被认为是复制构造函数,但不能作为移动构造函数。)

最新更新