在基类中删除复制和移动构造函数/赋值运算符就足够了吗?

9
如果我有一个抽象基类,并且我想使所有派生类都不可复制和不可移动,那么在基类中声明这些特殊成员函数已删除是否足够?我想确保我的整个类层次结构都是不可复制和不可移动的,我想知道是否可以不必在每个派生类中声明这4个特殊成员函数已删除而达到此目的。我看过一个 Stack Overflow 的答案,似乎暗示派生类可以显式声明一个复制或移动构造函数,尽管它被从基类中删除,但当我尝试定义默认的复制赋值运算符时,以下示例会导致编译错误,因此我不确定。这是错误信息:
// base_class.h
class BaseClass {
public:
  BaseClass(const BaseClass &) = delete;
  BaseClass(BaseClass &&) = delete;
  BaseClass &operator=(const BaseClass &) = delete;
  BaseClass &operator=(BaseClass &&) = delete;
  virtual ~BaseClass() = default;
  virtual bool doSomething() = 0;

protected:
  BaseClass(std::string name);

private:
  std::string name_;
};

// derived_class.h
class DerivedClass : public BaseClass {
public:
  DerivedClass();
  DerivedClass(const DerivedClass &);
  bool doSomething() override;
};

// derived_class.cc
DerivedClass::DerivedClass(const DerivedClass &) = default;
3个回答

9

无法阻止子类定义其自己的复制/移动构造函数。也就是说,如果你不提供一个,或者使用内联默认构造函数,它也将被标记为已删除,从而“开箱即用”地防止了这种情况。当你尝试只定义默认构造函数时,会在这里出现错误,因为当成员或基类已经隐式删除时,在外部定义中不允许这样做。

class DerivedClass : public BaseClass {
public:
  DerivedClass(const DerivedClass &) = default;
  bool doSomething() override;
};

如果这样做,代码会被编译通过,只有在您实际尝试调用复制构造函数时才会出错。这是因为即使成员或基类隐式删除了它,内联隐式默认值也是允许的,并且最终的结果是构造函数被隐式删除。


我正在遵循来自https://abseil.io/tips/131的指南,这就是为什么在第一次声明后我使用了`=default`,在这种情况下似乎效果很好。如果我像您一样在第一次声明中添加了`=default`,那么编译将成功,但实际上不会生成复制构造函数。 当您尝试仅定义构造函数为默认值时,在此处出现错误的原因是因为您不允许在外部定义中这样做。 我觉得以上说法并不正确。这是被允许的,但在这种情况下不行。 - Mike Sweeney
1
@MikeSweeney 我已经更新了措辞,使其更加明确。 - NathanOliver
谢谢。我已经重新阅读了你的原始回答几次,你说得很对,但我认为对于大多数提出这个问题的人来说,它太微妙了,无法理解。现在是一个很好的答案。 - Mike Sweeney
1
@MikeSweeney 谢谢。很高兴能帮忙。 - NathanOliver

6
删除基类中的复制和移动构造函数/赋值运算符足以防止隐式生成的复制和移动构造函数/赋值运算符。
可以设置为final来防止派生类显式声明复制或移动构造函数。这样就不能有派生类,因此派生类无法被复制。
当然,这样显式声明的复制构造函数(和其他函数)将无法复制不可复制的基础子对象。如果它们要使用,则必须使用BaseClass(std :: string)进行构造,并且赋值运算符不能以任何方式修改基础对象的状态(除非它们使用某些技巧绕过访问限定符封装)。

如果基类是可复制的,并且我想定义自己的 DerivedClass 复制构造函数,那么我该如何在其中调用 BaseClass 的复制构造函数? - Mike Sweeney
2
如果基类是可复制的,那么语法就是Derived(Derived const& other) : Base(other) , any other driveled members { body }。@MikeSweeney - NathanOliver

4

你无法阻止派生类声明复制/移动构造函数,但它们不能被默认生成:默认的复制构造函数或者派生类会尝试调用其基类的复制构造函数(移动也同理)。

但是,派生类可以使用其默认构造函数明确地构造其基类:

class DerivedClass : public BaseClass {
public:
  DerivedClass();
  DerivedClass(const DerivedClass &): BaseClass() {
      // copy ctor for the derived part
  }
  bool doSomething() override;
};

看,现在DerivedClass类可以复制了,尽管它的基类不能复制!


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