I have been analysing the problem of API design in C++ and how to work around a big hole in the language when it comes to separating interfaces from implementations.
I am a purist and strongly believe in neatly separating the public interface of a system from any information about its implementation. I work daily on a huge codebase that is not only really slow to build, mainly because of header files pulling a large number of other header files, but also extremely hard to dig as a client what something does, as the interface contains all sorts of functions for public, internal, and private use.
My library is split into several layers, each using some others. It's a design choice to expose to the client every level so that they can extend what the high level entities can do by using lower level entities without having to fork my repository.
And now it comes the problem. After thinking for a long while on how to do this, I have come to the conclusion that there is literally no way in C++ to separate the public interface from the details for a class in such a way that satisfies all of the following requirements:
It does not require any code duplication/redundancy. Reason: it is not scalable, and whilst it's OK for a few types it quickly becomes a lot more code for realistic codebases. Every single line in a codebase has a maintenance cost I would much prefer to spend on meaningful lines of code.
It has zero overhead. Reason: I do not want to pay any performance for something that is (or at least should!) be well known at compile time.
It is not a hack. Reason: readability, maintainability, and because it's just plain ugly.
As far as I know, and this is where my question lies, in C++ there are three ways to fully hide the implementation of a class from its public interface.
- Virtual interface: violates requirements 1 (code duplication) and 2 (overhead).
- Pimpl: violates requirements 1 and 2.
- Reinterpret casting the this pointer to the actual class in the .cpp. Zero overhead but introduces some code duplication and violates (3).
C wins here. Defining an opaque handle to your entity and a bunch of function that take that handle as the first argument beautifully satisfies all requirements, but it is not idiomatic C++. I know one could say "just use C-style while writing C++" but it does not answer the question, as we are speaking about an idiomatic C++ solution for this.