Home > Blockchain >  Should a C interface (abstract class with just pure virtual functions) delete the copy/move assign
Should a C interface (abstract class with just pure virtual functions) delete the copy/move assign

Time:01-17

I have a a lot of public interfaces (actually abstract classes with just pure-virtual functions). Only the destructor is marked as default but wouldn't it be more cleaner to delete the copy/move constructors and copy/move assignment operators? Is there actually a guideline for such "interfaces" that one should delete these constructors/assignment operators? Like:

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

CodePudding user response:

Copying is about data. Since here there are no data members trying to do anything about copy/move semantics makes no sense.

CodePudding user response:

There is no definite correct answer to this question. It depends on the intended use case of the interface.

As a guideline, the C Core Guidelines recommends to explicitly delete copy/move semantics in an inheritance hierarchy to drastically reduce the probability for accidental object slicing. It is hard be confident that the copied object is actually the most derived object. If copy semantics is desired for a polymorphic object, then it is suggested to add a virtual function to clone the object.

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 */ }
}

CodePudding user response:

Copy/move assignment can cause problems in actual code as follows:

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

this is an example of slicing, but we are doing a nonsense slice assign.

In theory you can write a polymorphic assignment:

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

but the situations where this makes sense are limited. Here are a few:

  1. This interface exists as a contract for a single fixed implementation. As such, standard assignment semantics are easy to implement.
  2. Assignment semantics are being replaced for this class because we are using the embedded sublanguage technique.

For cases other than this, sensible assignment at the interface level is difficult to justify, as if the two assigned objects disagree on their type, assignment is unlikely to give good semantics.

For the constructor case, no attempt to directly use a constructor of an abstract class can succeed already. Any use of the constructor will be in a context where there is an instance of a child class actually being constructed.

It is plausible that some work needs to be done -- some impurity -- in the interface. For example, if every instance of that interface needs to have its identity logged centrally, implementing constructors makes sense.

This is also a rare case.

In 999/1000 cases, =deleteing them is the correct move. C 's built-in object polymorphism doesn't play nice with assignment or copy/move construction. This is one of the reasons why people replace its use with versions that do play nice with it, like std::function.

  • Related