Restricting an interface in C++
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:   Share this post!