Jonathan Boccara's blog

Restricting an interface in C++

Published April 7, 2017 - 6 Comments

My colleague and friend Jonathan came up to me the other day with this request: “Jonathan”, he said – you won’t have too many names to remember to follow this story – “Jonathan, how would you go about restricting the methods of an interface? We’ve got a class which we use in a broader context, that we want to pass to a more specific one that doesn’t need all its functionality. How to prevent the specialized context from depending on the methods it doesn’t use, and to do so expressively in C++, please?” He had said the magic word. Not please, mind you, but expressively.

This got us thinking about this interesting issue, experimenting with solutions and comparing them, weighing the pros and the cons of each one of them. I’ve exposed them here, for you to forge your opinion about what to choose when you find yourself in a similar situation.

For the sake of the example, let’s use a simple Rectangle class, that has the following features:

class Rectangle
{
public:
    Rectangle(Position p, Width w, Height h);
    
    double getArea() const;
    double getPerimeter() const;

    void draw(Canvas&);

private:
    Position position_;
    double width_;
    double height_;
};

(Wonder how to easily define explicit types such as Weight and Height? These are called strong types, and I’ve got a whole series dedicated to them)

Here we want to pass this object to the part of the application that is focused on UI, and somehow keep the draw method but prevent the UI from seeing getArea and getPerimeter.

It is important to define what “seeing” means in this context. It can be two things:

  • the ability to use the methods. Let’s call this functional dependency. Breaking this dependency guarantees that the using code won’t use them, so if you change them you won’t have to change this code. This is useful if it’s your own code, and even more so if it is client code that you can’t reach to update.
  • the awareness that these methods exist. Avoiding this means that the calling code doesn’t even have to recompile if the methods interfaces change. For this reason we will call this compilation dependency. This is stronger than functional dependency because breaking compilation dependency also breaks functional dependency.

A basic solution: wrapping the Rectangle

The first solution that may come to your mind is creating a class over Rectangle that provides selective access to its methods:

#include "Rectangle.hpp"

class DrawableRectangle
{
public:
    explicit DrawableRectangle(Rectangle const& rectangle) : rectangle_(rectangle) {}
    void draw(Canvas& canvas) { rectangle_.draw(canvas); }

private:
    Rectangle rectangle_;
};

This class allows breaking functional dependency with the getArea and getPerimeter methods, because a piece of code manipulating a DrawableRectangle cannot access these methods nor retrieve the underlying Rectangle object with which it was created.

However, it doesn’t break compilation dependency because a user of DrawableRectangle will need to indirectly #include the class Rectangle and will therefore need to be recompiled whenever the interface of getArea changes for example, even if it sure not to use it. Also, there is arguably a lot of code for just saying that you want to reuse a method of  Rectangle, and this gets even more noticeable when you have several methods that you want to keep.

Pros:

  • Simple, can be understood by virtually any C++ developer

Cons:

  • verbose
  • compilation dependency

A cute solution: saying just what you mean

The DrawableClass from above is implemented in terms of Rectangle. As explained in Item 38 of Scott Meyers’ Effective C++, there are two ways in C++ to express the fact of being implemented in terms of something: composition, like above, and private inheritance.

Private inheritance allows the derived class to use anything public from the base class, but doesn’t expose anything from it in its public interface. Unless you specify it explicitly, that is:

#include "Rectangle.hpp"

class DrawableRectangle : private Rectangle
{
public:
    explicit DrawableRectangle(Rectangle const& rectangle) : Rectangle(rectangle) {}
    using Rectangle::draw;
};

Usually composition is preferred over private inheritance because private inheritance makes code more complex and tightly coupled. In this particular use case though, private inheritance allows you to elegantly declare just what you mean: a given method you want to expose can be made visible simply with a using. Compare this to the previous solution, and notice how much boilerplate went away.

Pros:

  • elegant and expressive: just mention which method you want to keep

Cons:

  • maybe slightly unsettling for developers who are not familiar with private inheritance
  • compilation dependency still there

A classical solution: the pimpl

If you only need to break functional dependency, one of the two above solution will do the job. But to break compilation dependency, more work is needed.

Let’s take the first solution and replace the Rectangle attribute in the DrawableRectangle by a pointer to Rectangle. This way you won’t have to #include the file where Rectangle is defined. A forward declaration will be enough. To relieve ourselves from the burden of managing the deletion of this pointer, we encapsulate it in a smart pointer that will do it for us, here an std::unique_ptr:

class Rectangle;

class DrawableRectangle
{
public:
    explicit DrawableRectangle(Rectangle const& rectangle);
    void draw(Canvas& canvas);

private:
    std::unique_ptr<Rectangle> rectangle_;
};

The methods are then implemented in a separate file DrawableRectangle.cpp that includes Rectangle.hpp, but a client of DrawableRectangle never includes Rectangle.hpp. This effectively breaks the compilation dependency to the getArea and getPerimeter methods.

However this comes at a cost. First this requires more work from your part as the developer of the DrawableRectangle class. For example you need to take care of such things as the copy constructor and copy assignment operator (operator=), by probably performing a deep copy of the Rectangle pointed to by the unique_ptr. The point of this article is not to present all the subtleties of the implementation of a pimpl, though. If your are interested in getting more in depth in this topic you can find excellent resources available such as the series of items about this in Exceptional C++ from Herb Sutter.

The pimpl method also incurs a performance cost: the unique_ptr wraps a pointer that is constructed with a new and disposed of by a delete, whereas the previous solutions kept a local copy of the underlying Rectangle object. Naturally you would need a profiler to prove that this is concretely a problem in your code, but system calls such as new and delete have been seen to be bottlenecks for performance when they are called a repeated number of times.

Pros:

  • compilation dependency

Cons:

  • more work to implement
  • potential impact on performance

Stepping back: wrapping it the other way around

Then we were suggested a different approach to the problem: maybe the fact that we need to extract things from Rectangle indicates that it is doing too many things.

Indeed this class does two sort of things: computational work such as working out the area and perimeter, and graphical work such as drawing itself on a canvas. A class implementing two responsibilities is a bad sign in design. Seen from this perspective, the Rectangle class could delegates these responsibilities to two separate classes: RectangleCalculator and RectangleDrawer:

// file RectangleCalculator.hpp

class RectangleCalculator
{
public:
    RectangleCalculator(Width w, Height h);
    double getArea() const;
    double getPerimeter() const;
private:
    double width_;
    double height_;
};

// file RectangleDrawer.hpp

class RectangleDrawer
{
public:
    RectangleDrawer(Position p, Width w, Height h);
    void draw(Canvas&);
private:
    Position position_;
    double width_;
    double height_;
};

// file Rectangle.hpp

#include "RectangleCalculator.hpp"
#include "RectangleDrawer.hpp"

class Rectangle
{
public:
    Rectangle(Position p, Width w, Height h);
    RectangleCalculator const& getCalculator() const;
    RectangleDrawer const& getDrawer() const;
private:
    RectangleCalculator calculator_;
    RectangleDrawer drawer_;
};

The Rectangle can then provide its RectangleDrawer part to the UI part of the application without it having to #include more than the file RectangleDrawer.hpp, that does not contain getArea and getPerimeter. And without allocating dynamic memory. And in fact, it would be beneficial to split up the responsibilites of the Rectangle further, because at this point the data (height and witdth) is duplicated. So we could consider separating the various behaviours from the common data here.

Restricting an interface

So in the general case, if the methods we’re trying to keep are in fact constituting one responsibility of the object, AND if we have the possibility to change the interface, then separating concerns seems like the soundest solution.

If it’s not the case, several wrapping solutions are available, each one with its own advantages and drawbacks. Then you decide what is worth paying for.

Thanks Jonathan for bringing up such an interesting topic!

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed