I've been playing with the Pimpl idiom and reaping all sorts of benefits from it. The only thing I haven't been too keen on is the feeling I get when I define the functions.
- Once in the header (P def)
- Once at the top of the .cpp (Impl def)
- Once in the middle of the .cpp (Impl Impl)
- Once at the lower end of the .cpp (P Impl)
I really enjoy cutting down code disparity and redundancy, and I feel like my code is less than well oiled when I have to add or change functions in even relatively complex Impls in my current project.
My question is, what effective ways are there to imply or template my classes in such a way that if I were to define a new function, I'd only have to write one explicit definition and implementation, and have the rest remain spatially close to the explicits in code; and if I were to change a function, the changes necessary would be as few as possible?
Let's postulate your header starts something like this:
Then when you add functions you have a choice to make:
do you have the
X
member function definition implement the logic, referring top_impl_->
things all over the place, orreturn p_impl->same_fn(all_the_args);
and keep the logic inside theImpl
class?If you choose 1. then you end up with a function declaration in the header, and a (slightly messier than usual) definition in the matching implementation file.
If you choose 2. then you end up with a function declaration in the header file, a wrapping/forwarding definition in the matching implementation file, and at a minimum a definition in the
Impl
structure (I tend not to define the functions outside theImpl
class definition - it's an implementation detail and the interface is not public anyway).There is no generally desirable way to improve on this situation (i.e. macro hackery and extra code-generation scripts in your build process may occasionally be warranted, but very rarely).
It may not matter a whole heap, though it may be of interest that a variation on the second approach is to first implement a class that doesn't use the pimpl idiom (complete with proper header and optionally inline functions), you can then wrap it with a pimpl management object and forward functions to it, and in that way you keep the freedom to have some code somewhere some day decide it wants to use the functionality without using the pimpl wrapper, perhaps for improved performance / reduced memory usage at the cost of the recompilation dependency. You can also do this to make use of a specific instantiation of a template without exposing the template's code.
To illustrate this option (as requested in a comment), let's start with a silly non-pimpl
class X
in its own files, then create aPimpl::X
wrapper (the use of namespace and the same class name is entirely optional but facilitates flipping client code to use either, and a reminder - this isn't meant to be concise, the point here is to let a non-pImpl version be usable too):If you opt for the above variation on 2, which makes the "implementation" a usable entity in it's own right, then yes - you may end up with 2 declarations and 2 definitions related to a single function, but then one of the definitions will be a simple wrapper/forwarding function which is only significantly repetitive and tedious if the functions are very short and numerous but have lots of parameters.
I'm just going to start by sumarizing to make sure I understand: You like the benefits of using pimpl, but dislike the amount of boilerplate code when adding or modifying functions?
In a nutshell, there is no template magic you can use to eliminate this boilerplate, but there are things to consider here as well:
You might consider something along these lines:
An Interface class to minimize repeating declarations. The client will use the
PublicImplementation
class in their code.Pimpl.h
Pimpl.cpp
And finally this is what the client code does:
Main.cpp
There's no requirement to treat the IMPL object to the same rules & standards as an object declaration in the .h file. By allowing member variables to be public (via a
struct
declaration), you don't need to implement an unnecessary wrapper layer. This is generally safe, since only the .cpp file has access to IMPL anyway.Consider the following code that achieves the benefits of the PIMPL idiom without unnecessary code duplication: