C++ 接口(仅具有纯虚函数的抽象类)是否应该删除复制/移动赋值构造函数

问题描述 投票:0回答:3

我有很多公共接口(实际上是只有纯虚函数的抽象类)。只有析构函数被标记为默认,但是删除复制/移动构造函数和复制/移动赋值运算符不是更干净吗?对于这样的“接口”实际上是否有一个指导方针,要求人们删除这些构造函数/赋值运算符?喜欢:

class MyInterface
{
  public:
    virtual ~MyInterface() = default; 
    MyInterface(const MyInterface&) = delete;
    MyInterface(const MyInterface&&) = delete;
    MyInterface& operator=(const MyInterface&) = delete;
    MyInterface& operator=(const MyInterface&&) = delete;
 
    [[nodiscard]] virtual std::string getName() const = 0;
    ...
};
c++ constructor abstract-class
3个回答
3
投票

复制是关于数据的。因为这里没有数据成员尝试对复制/移动语义执行任何操作,这是没有意义的。


2
投票

这个问题没有明确的正确答案。这取决于界面的预期用例。

作为指导,C++ 核心指南建议显式删除继承层次结构中的复制/移动语义,以大幅降低意外对象切片的可能性。很难确信复制的对象实际上是最派生的对象。如果多态对象需要复制语义,那么建议添加

virtual
函数来克隆该对象。

template<class T> using owning = T;

class Base
{
    Base(const Base&) = delete;
    Base(Base&&) = delete;
    Base& operator=(const Base&) = delete;
    Base& operator=(Base&&) = delete;

    virtual ~Base() = default;

    virtual owning<Base*> clone() const = 0;
};

class Derived : public Base
{
    owning<Derived*> clone() const override { /* explicit copy */ }
};

// There can be a long chain of derived classes
class Derived2 : public Derived
{
    owning<Derived2*> clone() const override { /* impl */ }
}

0
投票

复制/移动赋值可能会导致实际代码出现问题,如下所示:

void some_func( MyInterface* lhs, MyInterface* rhs ) {
  *lhs = *rhs;
}

这是一个切片的例子,但我们正在做一个无意义的切片分配。

理论上你可以写一个多态赋值:

  MyInterface& operator=(MyInterface const& rhs)const&{
    AssignFrom(rhs);
  }
  virtual void AssignFrom(MyInterface const& rhs)const&=0;

但是这种做法有意义的情况是有限的。 这里有一些:

  1. 此接口作为单个固定实现的契约而存在。 因此,标准赋值语义很容易实现。
  2. 此类的赋值语义正在被替换,因为我们正在使用嵌入式子语言技术。

对于除此之外的情况,很难证明接口级别的合理分配是合理的,因为如果两个分配的对象在类型上不一致,则分配不太可能提供良好的语义。

对于构造函数的情况,直接使用抽象类的构造函数的尝试已经不可能成功。 构造函数的任何使用都将在实际构造子类实例的上下文中进行。

界面中可能需要做一些工作——一些杂质。 例如,如果该接口的每个实例都需要集中记录其身份,那么实现构造函数就有意义。

这也是一个罕见的案例。

在 999/1000 的情况下,

=delete
是正确的举动。 C++ 的内置对象多态性不能很好地处理赋值或复制/移动构造。 这就是为什么人们用可以很好用的版本替换它的原因之一,比如
std::function

© www.soinside.com 2019 - 2024. All rights reserved.