我有很多公共接口(实际上是只有纯虚函数的抽象类)。只有析构函数被标记为默认,但是删除复制/移动构造函数和复制/移动赋值运算符不是更干净吗?对于这样的“接口”实际上是否有一个指导方针,要求人们删除这些构造函数/赋值运算符?喜欢:
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++ 核心指南建议显式删除继承层次结构中的复制/移动语义,以大幅降低意外对象切片的可能性。很难确信复制的对象实际上是最派生的对象。如果多态对象需要复制语义,那么建议添加
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 */ }
}
复制/移动赋值可能会导致实际代码出现问题,如下所示:
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;
但是这种做法有意义的情况是有限的。 这里有一些:
对于除此之外的情况,很难证明接口级别的合理分配是合理的,因为如果两个分配的对象在类型上不一致,则分配不太可能提供良好的语义。
对于构造函数的情况,直接使用抽象类的构造函数的尝试已经不可能成功。 构造函数的任何使用都将在实际构造子类实例的上下文中进行。
界面中可能需要做一些工作——一些杂质。 例如,如果该接口的每个实例都需要集中记录其身份,那么实现构造函数就有意义。
这也是一个罕见的案例。
在 999/1000 的情况下,
=delete
是正确的举动。 C++ 的内置对象多态性不能很好地处理赋值或复制/移动构造。 这就是为什么人们用可以很好用的版本替换它的原因之一,比如std::function
。