具有不可移动成员的自动生成移动构造函数

5

我遇到了一个非常有趣的情况,我的代码编译通过了,尽管我感到很惊讶,所以我想请你发表一下你的看法。

情况是这样的。我有一个类,其中删除了移动和复制构造函数,并具有用户定义的赋值运算符:

struct A {
    A() { }
    A(const A&) = delete;
    A(A&& ) = delete;

    A& operator=(const A& ) { return *this; }
    A& operator=(A&& ) { return *this; }
};

我还有另一个类,只有 A 作为成员。在这个类中,我定义了拷贝构造函数,但将移动构造函数保持为默认,并通过调用 swap 函数定义赋值运算符:

class B{
public:
    A a;

    B()
    : a{}
    { }

    B(const B&)
    : a{}
    { }

    B(B&& other) = default;
};

int main() {
    B b1;
    B b2(std::move(b1)); // compiles??
}

默认移动构造函数为什么有效,考虑到它不能简单地调用移动或复制构造函数A? 我正在使用gcc 4.8.4.

1个回答

7

我的原始答案是错误的,所以我要重新开始。


在[class.copy]中,我们有:

如果类X具有以下内容,则默认情况下为类X定义的复制/移动构造函数被定义为已删除(8.4.3):
— [...]
— 一个可能构造的子对象类型M(或其数组),由于重载分辨率(13.3)应用于M的相应构造函数而无法复制/移动,导致二义性或函数是已删除或从默认构造函数不可访问。
— [...]

这个符号点适用于B(B&& other) = default;,因此该移动构造函数被定义为已删除。这似乎会破坏使用std::move()编译,但我们也有(通过解决缺陷1402):

默认情况下定义为已删除的移动构造函数被重载分辨率(13.3、13.4)忽略[注: 否则,已删除的移动构造函数将干扰可以使用复制构造函数来初始化的右值。 —end note]

忽略是关键。因此,当我们执行以下操作时:

B b1;
B b2(std::move(b1));

虽然 B 的移动构造函数被删除,但这段代码仍然是良好的,因为移动构造函数不参与重载决议,而是调用复制构造函数。因此,即使您无法通过其移动构造函数构造 B,它仍然是可移动构造的。

我在复制和赋值中添加了一些couts,似乎证实了您的假设,即无法正确编译移动构造函数,它会简单地回退到复制。我不是标准细节的专家,但默认声明的行为看起来像模板中的SFINAE。 - Triskeldeian
一个被定义为删除的默认移动构造函数会被重载决议所忽略。请参见N4527 [class.copy]/p11; 还有N3667 - T.C.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接