Suppose I have the following interface:
struct Person {
std::string name;
unsigned short age;
};
class ContainerInterface {
public:
virtual ~ContainerInterface () = default;
virtual void addPerson (Person const&) = 0;
virtual std::optional<Person> getPerson (std::string const& name) const = 0;
virtual bool hasPerson (std::string const& name) const = 0;
};
This interface could be implemented in the following way:
class Container: public ContainerInterface {
public:
virtual void addPerson (Person const& person) override {
_people.push_back(person);
}
virtual std::optional<Person> getPerson (std::string const& name) const override {
for (const auto& person: _people) {
if (person.name == name) return person;
}
return std::nullopt;
}
virtual bool hasPerson (std::string const& name) const override {
return getPerson(name).has_value();
}
private:
std::vector<Person> _people;
};
While this looks straightforward, there is a catch: methods like hasPerson are really just an alias for getPerson(name).has_value(). All implementations of ContainerInterface should share that, it shouldn't be left to the implementation itself to enforce. In fact, nothing stops the implementation from doing something like this:
class BrokenContainer: public Container {
public:
virtual bool hasPerson (std::string const& name) const override {
return false;
}
};
Sure, I can fix this by implementing hasPerson as part of ContainerInterface:
class ContainerInterface {
public:
virtual ~ContainerInterface () = default;
virtual void addPerson (Person const&) = 0;
virtual std::optional<Person> getPerson (std::string const& name) const = 0;
virtual bool hasPerson (std::string const& name) const final;
};
// Should always do this, regardless of implementation
bool ContainerInterface::hasPerson (std::string const& name) const {
return getPerson(name).has_value();
}
But then my ContainerInterface is not a pure interface anymore. In some production settings, there are macros that I could stick to my interface to mark it a pure interface, and that check that it doesn't implement any methods. If I use this approach I wouldn't be able to mark my class as a pure interface.
Another workaround would be to implement hasPerson as a free function:
bool hasPerson (ContainterInterface const& container, std::string const& name) {
return container.getPerson(name).has_value();
}
But that doesn't feel as satisfactory, since hasPerson sounds like it should be a method of ContainerInterface.
Is there any more elegant way of keeping ContainerInterface a pure interface while enforcing that all implementations share the same meaning to hasPerson?
A workaround is to not let other classes inherit from
ContainerInterfacedirectly.This is essentially the same as giving the interface a default implementation that can't be overridden: