强制显式默认的特殊成员函数生成

Enforcing explicitly defaulted special member function generation

本文关键字:成员 函数 默认      更新时间:2023-10-16

在C++11中,如果自动阻止了特殊成员函数的隐式生成,则可以显式默认该函数。

但是,显式默认特殊成员函数

只会撤消手动声明其他一些特殊成员函数(复制操作、析构函数等(导致的隐式删除,它不会强制编译器生成函数,即使实际上无法生成函数,代码也被认为是格式良好的。

请考虑以下方案:

struct A
{
    A ()         = default;
    A (const A&) = default;
    A (A&&)      = delete;  // Move constructor is deleted here
};
struct B
{
    B ()         = default;
    B (const B&) = default;
    B (B&&)      = default; // Move constructor is defaulted here
    A a;
};

编译器不会生成 B 中的移动构造函数,因为这样做会导致编译错误(A 的移动构造函数被删除(。如果不显式删除 A 的构造函数,B 的移动构造函数将按预期生成(复制 A,而不是移动它(。

尝试移动此类对象将以静默方式改用复制构造函数:

B b;
B b2 (std::move(b)); // Will call B's copy constructor

有没有办法强制编译器生成函数或在不能时发出编译错误?如果没有此保证,如果单个已删除的构造函数可以禁用整个对象层次结构的移动,则很难依赖默认的移动构造函数。

有一种方法可以检测像 A 这样的类型。但仅当类型显式删除移动构造函数时。如果移动构造函数隐式生成为已删除,则它不会参与重载解析。这就是为什么B是可移动的,即使A不是。 B default移动构造函数,这意味着它会被隐式删除,因此会发生复制。

因此,B是可移动的。然而,A不是。所以这是一个简单的问题:

struct B
{
    static_assert(is_move_constructible<A>::value, "Oops...");
    B ()         = default;
    B (const B&) = default;
    B (B&&)      = default; // Move constructor is defaulted here
    A a;
};

现在,没有通用的方法可以使任何包含仅复制类型的类型执行您想要的操作。也就是说,您必须单独对每种类型进行静态断言;不能在默认的移动构造函数中放置一些语法来尝试移动B失败。

其原因部分与向后兼容性有关。考虑一下声明用户定义复制构造函数的所有 C++11 之前的代码。根据 C++11 中移动构造函数生成的规则,所有这些构造函数都将删除移动构造函数。这意味着表单 T t = FuncReturningTByValue(); 的任何代码都会失败,即使它在 C++98/03 中通过调用复制构造函数工作得很好。因此,如果无法生成移动构造函数,则通过制作这些副本而不是移动来解决此问题。

但由于= default的意思是"做你通常会做的事情",它还包括这种特殊的重载解析行为,该行为忽略隐式删除的移动构造函数。