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

Enforcing explicitly defaulted special member function generation

在 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 意味着 "do what you would normally do",它还包括忽略隐式删除的移动构造函数的特殊重载解析行为。