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

jljoyd4f  于 2023-01-18  发布在  其他
关注(0)|答案(3)|浏览(164)

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

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;
    ...
};
0s7z1bwu

0s7z1bwu1#

复制是关于数据的。由于这里没有数据成员试图做任何关于复制/移动语义的事情是没有意义的。

mkshixfv

mkshixfv2#

这个问题没有明确的正确答案,这取决于接口的预期用例。
C++ Core Guidelines建议显式删除继承层次结构中的复制/移动语义,以大幅降低意外object slicing的可能性。很难确信复制的对象实际上是最派生的对象。如果多态对象需要复制语义,则建议添加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 */ }
}
wmtdaxz3

wmtdaxz33#

复制/移动分配可能会在实际代码中导致以下问题:

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.这个接口作为一个单一的固定实现的契约而存在,因此,标准的赋值语义很容易实现。
1.这个类的赋值语义被替换了,因为我们使用了嵌入式子语言技术。
对于除此之外的其他情况,在接口级别进行合理的赋值是很难证明的,因为如果两个赋值对象的类型不一致,赋值就不可能给予好的语义。
对于构造函数的情况,任何直接使用抽象类的构造函数的尝试都不可能成功。构造函数的任何使用都将在实际构造子类示例的上下文中进行。
在接口中需要做一些工作--一些杂质--这似乎是合理的。例如,如果接口的每个示例都需要集中记录其标识,则实现构造函数是有意义的。
这也是一个罕见的案例。
在999/1000种情况下,=delete ing是正确的选择。C++内置的对象多态性在赋值或复制/移动构造中表现不佳。这就是为什么人们用std::function这样的版本来取代它的原因之一。

相关问题