类似乎可以使用用户定义的析构函数移动
我想弄清楚为什么这段代码不能编译?我已经创建了用户定义的析构函数,因此不应创建移动构造函数和移动赋值运算符。那么为什么复杂化失败,说我的班级不符合要求?
#include <iostream>
#include <concepts>
#include <type_traits>
template<class T> requires (!std::movable<T>)
struct Singleton
{
};
struct Widget
{
~Widget() = default;
};
int main()
{
auto x = Singleton<Widget>{};
}
编辑。这个版本也是如此。
#include <iostream>
#include <concepts>
#include <type_traits>
extern void fun();
template<class T> requires (!std::movable<T>)
struct Singleton
{
};
struct Widget
{
~Widget()
{
fun();
}
};
int main()
{
auto x = Singleton<Widget>{};
}
错误信息
:23:28: 注意: 不满足约束: 替换“模板需要 !(movable) > struct Singleton [with T = Widget]”: :23:28: 从这里需要 :8:8: 需要的约束'模板需要 !(movable) struct Singleton' :7:30: 注意:表达式 '!(movable) [with T = Widget]' 评估为 'false' 7 | 模板需要 (!std::movable)
在 Godbolt 上编译使用的 gcc (trunk)。我使用的唯一编译标志是 -std=c++20
https://godbolt.org/z/sb535b1qv
回答
在这两个示例中都没有移动构造函数。但这不是std::movable检查的内容。它推迟了您所测试的检查std::move_constructible。该概念仅检查对象是否可以从右值直接初始化和复制初始化。
从 C++98 时代起,复制构造函数就能够从右值进行初始化。它仍然适用于添加移动操作。这就是为什么特性得到满足的原因。Widget a; Widget b = std::move(a);即使没有移动构造函数,它仍然有效,因为编译器提供的复制构造函数使这成为可能。
编译器不生成移动构造函数是为了在迁移到 C++11 时保持旧代码正常工作而做出的设计决定。这样的代码将遵守三规则,因此将旧副本交换为编译器生成的移动被认为是有风险的。但是旧代码仍然可以从右值初始化对象,所以复制构造函数保留了它的旧函数。
值得注意的是,该特性在未来可能会如人们所期望的那样发挥作用。此处定义的默认复制构造函数是不推荐使用的功能:
6如果类定义未显式声明复制构造函数,则隐式声明非显式构造函数。如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的复制构造函数被定义为已删除;否则,它被定义为默认值 ([dcl.fct.def])。如果类具有用户声明的复制赋值运算符或用户声明的析构函数([depr.impldec]),则不推荐使用后一种情况。