将派生类标记为可移动而基类不可移动是否有意义\合适?

Is it meaningful\suitable to mark the derived class as movable while the base class is non-moveable?

提问人:John 提问时间:6/11/2022 最后编辑:John 更新时间:6/11/2022 访问量:157

问:

将派生类标记为可移动而基类不可移动是否有意义\合适?

我知道这种不一致在 C++ 中是合法的,但它在实践中有意义\合适吗?

一般来说,我应该小心翼翼地保持这种一致性吗?

这种情况如何:当我打算将派生类标记为不可移动和不可复制时,我是否也应该将基类标记为不可移动和不可复制?

我做了几个测试来弄清楚。

这是第一个示例。由于基类是不可复制和不可移动的,因此派生类实际上是不可移动的,因为它有一个移动构造函数,这在我的期望中。提示:下面的代码片段无法编译。

#include <memory>
#include <string>
#include <iostream>

class Base {
public:
    Base(){}
    Base(const Base&) = delete;
    Base(Base&&)      = delete;
    Base& operator=(const Base&) = delete;
    Base& operator=(Base&&) = delete;
};

class Derived:public Base
{
public:
    Derived(){}
    Derived(const Derived&) = default;
    Derived(Derived&&)      = default;
    Derived& operator=(const Derived&) = default;
    Derived& operator=(Derived&&) = default;
};

int main()
{
    Derived derived;

    Derived derived1{std::move(derived)};
}

这是第二个示例。基类是可复制且不可移动的,但派生类实际上是可移动的,因为当调用派生类的移动构造函数时,它将调用基类的复制构造函数而不是基类的移动构造函数,这也是我的期望。提示:下面的代码片段效果很好。

#include <memory>
#include <string>
#include <iostream>

class Base {
public:
    Base(){}
    Base(const Base&) = default;
    Base(Base&&)      = delete;
    Base& operator=(const Base&) = default;
    Base& operator=(Base&&) = delete;
};

class Derived:public Base
{
public:
    Derived(){}
    Derived(const Derived&) = default;
    Derived(Derived&&)      = default;
    Derived& operator=(const Derived&) = default;
    Derived& operator=(Derived&&) = default;
};

int main()
{
    Derived derived;

    Derived derived1{std::move(derived)};
}

更新时间

多亏了@Nightlord,他发现上面的代码片段不能用 Clang 编译,这真的出乎我的意料。

C++ C++11 复制构造函数 move-constructor

评论

1赞 Aconcagua 6/11/2022
老实说,允许复制对我来说似乎根本没有意义,但明确不允许已经在基地中移动。如果我不再需要原始对象,为什么还要强制使用对象的副本?或者,如果复制成本高昂,则被迫使用动态分配(然后传递一个智能指针)?
0赞 user17732522 6/11/2022
我也想不出删除的移动构造函数的合理用例,而是默认的复制构造函数。它将禁止某些形式的构造对象,但具体而言,这些形式在 C++ 修订版之间会有所不同,这似乎令人困惑。我认为在 C++20 中,基本上只禁止从派生的右值或带支撑的初始值设定项列表参数(或其他返回右值引用的函数)直接初始化。否则,与仅将移动构造函数委托给复制构造函数没有什么不同。Basestd::move
0赞 John 6/11/2022
@user17732522 这种情况呢:当我打算将派生类标记为不可移动和不可复制时,我是否也应该将基类标记为不可移动和不可复制?所述基类可能用作接口。
1赞 user17732522 6/11/2022
@John 为什么派生类的这些属性会影响基类?基类不需要知道派生类在做什么。如果基类是一个接口,复制/移动它本身就没有意义,则将基类操作标记为已删除,否则则不删除。
0赞 Sedenion 6/11/2022
在实践中,当编写泛型类似容器的类型时,OP 中显示的模式可能会发生,并且应有条件地删除构造函数。例如 std::optional。在 C++20 概念之前,有条件删除是通过有条件地从不同的基类派生来实现的。其中一个基类将删除复制/移动构造函数,另一个基类将定义它。然后,最派生的类(例如)只是默认它(这意味着它要么被删除,要么不被删除)。但这不是典型的继承。std::optional

答:

1赞 Nightlord 6/11/2022 #1

我知道这种不一致在C++中是合法的,但事实确实如此 在实践中有意义吗?

一般来说,我应该小心翼翼地保持这种一致性吗?

是的,我认为你应该保持一致性。 若要使派生类可移动而基类不可移动,则基类不能包含任何数据成员,否则移动不会完成。

如果您的类不包含任何数据成员,则可能将其用作接口。移动构造函数不适用于这种情况,因为客户端只能识别接口(基类)而不是具体类(派生类)。

评论

0赞 John 6/11/2022
你的第二段对我来说是新的。谢谢。我将做一些测试以完全理解它。“要使派生类可移动,而基类不可移动,基类不能保存任何数据成员,否则移动未完成”是什么意思?即使基类中存在成员变量,它也确实有效。
0赞 Nightlord 6/11/2022
@John您好,在您的示例中,derived1 真正调用的是“Copy Constructor”而不是“Move Constructor”。Move 构造函数将不起作用,因为“str”属性由 Base 类维护,并且 Base 的 Move 构造函数被删除。因此,“str”是不可移动的。以下是您的示例的两个简单修改版本,它们显示了我所说的内容。1. 删除基类 2 的复制构造函数后无法编译str 不会移动,而是复制到 derived1。Movable 类显示了它应该如何工作
0赞 Nightlord 6/11/2022
我不确定为什么它调用复制构造函数而不是编译错误。我猜这是由编译器完成的,因为某些编译器在编译示例时显示编译错误。所以我不认为这是 C++ 标准中的默认行为。
0赞 John 6/11/2022
这两个示例代码片段的行为确实是例外。一些编译器在编译我的示例时显示编译错误?您能否用在线编译器重现这种现象?据我所知,应该没有任何错误
1赞 Nightlord 6/11/2022
下面是一个示例。我只是在您提供的链接上选择另一个编译器。