显式声明析构函数后,为何仍能生成C++移动操作?
疑问:为什么带用户声明析构函数的类仍被判定为可 noexcept 移动构造?
我查阅到C++标准草案N3337的[class.copy]/9条款:若类X未显式声明移动构造函数,仅当X无用户声明的拷贝构造函数、拷贝赋值运算符、移动赋值运算符、析构函数,且移动构造函数不会被隐式定义为删除时,才会被隐式声明为默认移动构造函数。另外,将析构函数声明为= default也属于用户声明。
但运行如下代码时:
struct A1 { virtual ~A1() {}; }; struct B1 : public A1 { ~B1() override {}; }; struct A2 { virtual ~A2() = default; }; struct B2 : public A2 { ~B2() override = default; }; struct A_NO_VIRTUAL { ~A_NO_VIRTUAL() {}; }; struct B_NO_VIRTUAL : public A_NO_VIRTUAL { ~B_NO_VIRTUAL() {}; }; int main() { std::cout << std::is_nothrow_move_constructible_v<B1> << std::is_nothrow_move_constructible_v<B2> << std::is_nothrow_move_constructible_v<B_NO_VIRTUAL> << std::endl; }
实际输出为111,但我预期是000,这是为什么?
核心原因:移动构造回退到拷贝构造,且拷贝构造是noexcept的
你预期000的逻辑是:因为这些类都有用户声明的析构函数,所以编译器不会隐式生成移动构造函数,因此is_nothrow_move_constructible应该返回false。但这里忽略了C++中移动操作的回退规则:
当类没有可用的移动构造函数时,编译器会尝试使用拷贝构造函数来完成移动操作——因为右值可以绑定到const T&类型的参数。而你的代码里的类满足两个关键条件:
- 所有类都没有用户声明的拷贝构造函数,编译器会隐式生成默认拷贝构造函数;
- 默认生成的拷贝构造函数是
noexcept的:因为类本身是空类,所有基类和成员的拷贝构造都不会抛出异常,符合默认拷贝构造函数的noexcept判定规则。
而std::is_nothrow_move_constructible_v的判定逻辑是:类型是否能通过右值不抛出地完成构造——不管是用专属的移动构造函数,还是用能接受右值的拷贝构造函数,只要满足这个条件就返回true。
所以你的代码里,虽然没有隐式生成移动构造函数,但隐式生成的拷贝构造函数可以不抛出地处理右值,因此这个 trait 会返回1。
如果想要让is_nothrow_move_constructible_v返回false,可以显式声明拷贝构造函数并标记为noexcept(false),或者显式删除移动构造函数,这样就不会回退到拷贝构造逻辑了。
内容的提问来源于stack exchange,提问作者dwto
相关产品推荐
相关产品推荐

