1

I'm trying to get my head around the rule of five when using interfaces(the concept anyway) and abstract classes and struggling to understand how the rules work.

Suppose I have a layout like this:

#include <memory>
#include <optional>
#include <string>

class IEventInterface {
    protected:
        IEventInterface() = default;
        
    public:
        virtual ~IEventInterface() = default;
        
        /* rule of 5 says i need these too (https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-five) */
        IEventInterface(const IEventInterface&) = delete;               // copy constructor
        IEventInterface& operator=(const IEventInterface&) = delete;    // copy assignment
        IEventInterface(IEventInterface&&) = delete;                    // move constructor
        IEventInterface& operator=(IEventInterface&&) = delete;         // move assignment
        
        virtual std::optional<std::string> getEventText() noexcept = 0;
        virtual bool isEnabled() noexcept = 0;
};

class AbstractEvent : public IEventInterface {
    public:
        AbstractEvent() : m_enabled { true } {}
        virtual ~AbstractEvent() = default;
        
        /* Do i need to disable the other copy/move operators here too? */
        
        bool isEnabled() noexcept override {
            return m_enabled;
        }
    private:
        bool m_enabled;
};

class EventToday final : public AbstractEvent {
    public:
        EventToday() = default;
        virtual ~EventToday() {
            // some additional cleanup steps are required so no default here
        }
        
        std::optional<std::string> getEventText() noexcept override {
            // some code here to get the event text....
        }
        std::unique_ptr<Collector> m_collector;
        
        /* Do i need to disable the other copy/move operators here too? */
};

in some other code I have a vector full of IEventInterface e.g. std::vector<std::unique_ptr<IEventInterface>> m_events;

Where is the correct place to enforce the rule of five rules? Since the EventToday class needs a destructor defined for some cleanup they will need to kick in but I'm not sure where? In the example above I have them in the interface class but I suspect this is wrong because there is no define or delete required for any of the copy/move/destructor in the interface or abstract class.

François Andrieux
  • 28,148
  • 6
  • 56
  • 87
incubus
  • 681
  • 1
  • 5
  • 21
  • can you explain what clean up is required? What is the resource that the concrete Events are managing? – 463035818_is_not_an_ai Jul 05 '21 at 20:21
  • 4
    Since everything inherits from `IEventInterface` which already `delete`s the move and copy constructors and the assignment operator, then `EventToday` is already not copyable and not moveable. Even if `EventToday` owned a ressource the rule of 5 is already respected. – François Andrieux Jul 05 '21 at 20:21
  • the two subclasses you show are fine with the rule of 0. Its not clear why you need to implement any of the 5. – 463035818_is_not_an_ai Jul 05 '21 at 20:23
  • Having a `default` destructor doesn't require you to define or delete the remaining 4. – Nathan Pierson Jul 05 '21 at 20:25
  • @FrançoisAndrieux sounds like an answer – alter_igel Jul 05 '21 at 20:56
  • @FrançoisAndrieux thanks. Would you say it was bad design to have it defined in the interface? Is it better to let the implementing class make these decisions? – incubus Jul 05 '21 at 22:51
  • @463035818_is_not_a_number This was just a hypothetical question to help me understand but if there was say, a thread running in the ```EventToday``` I would want to stop and join before terminating. – incubus Jul 05 '21 at 22:52
  • @NathanPierson there is a default destructor in the interface and abstract event but there is a defined one in the implementing class so I would need to implement the other 4.......I just wasn't sure where that should be. – incubus Jul 05 '21 at 22:56
  • @john It is usually an error to copy or move polymorphic types due to [object slicing](https://stackoverflow.com/questions/274626/what-is-object-slicing). One way of preventing it is to `delete` these 4 special members. However, a more flexible solution is to make those `protected` which would still allow leaf types (most derived types) to expose those operations in cases where their concrete type is fully known and object slicing cannot occur. – François Andrieux Jul 05 '21 at 23:00
  • @fran If they really want to resurrect copy/move, they can even if abstract has it deleted. I see no reason to make that accidentally easier, as doing so feels like code smell. Abstract interfaces sort of imply identity matters (as vtables are identity based), so default copy/move means incoherant semantics on any non-final class (final devirtualizes the vtable methods basically) – Yakk - Adam Nevraumont Jul 06 '21 at 03:14

0 Answers0