7

Recently, we had a discussion about code using protected inheritance in order to hide the fact (to client code) a class inherits from a specific base class but to exploit this fact in the implementation.

The following code demonstrates this. It compiles with recent versions of GCC and clang++ (it uses C++11 features):

#include <vector>
#include <iostream>

class IObserver
{
public:
    virtual void update() = 0;
};

class Model
{
    std::vector<IObserver*> m_observers;
    int m_number = 0;
public:
    void addObserver(IObserver& observer) {
        m_observers.push_back(&observer);
    }
    void setNumber(int value) {
        m_number = value;
        notifyObservers();
    }
    int number() const {
        return m_number;
    }
protected:
    void notifyObservers() {
        for (auto pObserver : m_observers)
            pObserver->update();
    }
};

// We want to hide the fact class 'View' has 'IObserver' interface.
class View : protected IObserver
{
    Model* m_pModel;
public:
    View(Model& model) : m_pModel(&model) {
        model.addObserver(*this); // Exploit the fact we are an 'IObserver'.
    }
protected:
    void update() override {
        std::cout << m_pModel->number() << std::endl;
    }
};

int main(int argc, char *argv[])
{
    Model model;
    View view(model);

    //view.update(); // ERROR: 'update' is a protected member of 'View'.

    model.setNumber(1);
    model.setNumber(2);
}

The 'View' class inherits 'IObserver' interface but uses 'protected' modifier. So correspondingly, the public method 'update' inherited from that interface is protected. In the constructor, class 'View' adds itself as an observer to the 'Model' instance passed in as a parameter. Running the executable will output '1' and '2' in two separate lines, so the code runs as expected.

Now this solution was intensely discussed in our team but in the end there were some questions we could not answer with a common agreement:

  1. Is this code actually legal w.r.t. to the C++ standard? The issue here is that, in the constructor, the 'View' class passes a reference to itself to method 'Model::addOberver' which expects a reference to an 'IObserver' instance with a public 'update' method. However, as the 'View' class uses protected inheritance, this method now is protected. So when 'Model::notifyObservers' method is called within 'Model::setNumber' method, the View's 'update' method will be called "from outside" in spite of the fact it is actually protected. (The C++ FAQ Lite states: "[protected inheritance] allows derived classes of the protected derived class to exploit the relationship to the protected base class". So this sounds like the code above is a corresponding use case.)
  2. Assumed it is legal code, is it also good design? The fact we discussed a lot and didn't come to a common agreement might be a hint that it is not. Some of our colleagues had the opinion that a class' interface is solely defined by its public methods. And if 'View' class wants to pass a reference to itself to a method expecting an 'IObserver' instance it must use public inheritance. Some others (including myself) didn't agree with this strict definition. The implementation knows about the fact it has (and can provide) an 'IObserver' interface, so why should it not exploit this knowledge and expose the protected interface to code it wants to? In C++ we are able to define both, a public interface for "normal" client code and a protected interface for client code that wants to specialize/extend the class. So from this point of view, a class' interface is defined by its public and its protected interface. It just depends on which kind of client code "looks at" a class' interface.
  3. If the goal is to hide away 'IObserver' interface from the public, is using the pImpl idiom solely for this purpose worth it? Because we were not able to come to a common conclusion regarding the first two questions we decided to "workaround" them by using the pImpl idiom and public inheritance in the implementation class. So we now have a class 'View' that does not inherit from 'IObserver' class at all. Instead, a class 'ViewImpl' now inherits from 'IObserver' publicly. While we agreed that this is "good design" it made our implementation more complex, we have one indirection more, and we now have to maintain nearly twice as much code. Furthermore, we need to maintain inheritance hierarchies for both, the public and the implementation classes. (Of course, these are well-known drawbacks from using the pImpl idiom.)

I appreciate your opinions regarding these three questions a lot!!! So many thanks in advance :-)

1 Answer 1

6
  1. We can establish that it is legal (i.e. valid) C++ because it compiles. You've used it on GCC and clang and I've done exactly same thing with MSVC++ so that covers the majority of the compiler market.

  2. This is why protected/private (btw, I would use private) modifier was introduced. It allows your class to implement callback interfaces which are needed for other classes it is using internally, but a) the rest of the world doesn't need to know that you are using those other classes and b) the rest of the world should not be able to even call on any of those functions because they are meant to be hidden and used only by internals.

    When I transitioned from C++ to C#, I actually felt weird that I couldn't have private interface inheritance. If something is an implementation detail and not intended to be seen/used by the public, it should be kept hidden and I think the method you described is a great and simple way of doing that.

  3. Using pImpl just generates more overhead. When you allocate your objects (obviously assuming dynamic allocation), you end up going to the heap twice as many times. You also reduce the locality of your code because now View and ViewImpl might be in different pages in memory and will cause more cache misses. And you've increased the amount of boiler plate code you have to write because now you are forwarding all public interface calls from the View to ViewImpl.

This is all just my opinion but in the end, I cannot think of any reason not to have private interface inheritance. I've written code like this for 6+ years and never had any readability/maintainability problems because of it.

Not the answer you're looking for? Browse other questions tagged or ask your own question.