Let's postulate your header starts something like this:
class X
{
public:
...la de dah...
private:
struct Impl;
Impl* p_impl_;
};
Then when you add functions you have a choice to make:
do you have the X
member function definition implement the logic, referring to p_impl_->
things all over the place, or
return p_impl->same_fn(all_the_args);
and keep the logic inside the Impl
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 the Impl
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 a Pimpl::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):
// x.h
class X
{
public:
int get() const { return n_; } // inline
void operator=(int); // out-of-line definition
private:
int n_;
};
// x.c++
#include <x.h>
void X::operator=(int n) { n_ = n * 2; }
// x_pimpl.h
namespace Pimpl
{
class X
{
public:
X();
X(const X&);
~X();
X& operator=(const X&);
int get() const;
void operator=(int);
private:
struct Impl;
Impl* p_impl_;
};
}
x_pimpl.c++
#include <x.h>
namespace Pimpl
{
struct X::Impl
{
::X x_;
};
// the usual handling...
X() : p_impl_(new Impl) { }
X(const X& rhs) : p_impl(new Impl) { p_impl_->x_ = rhs.p_impl_->x_; }
~X() { delete p_impl_; }
X& operator=(const X& rhs) { p_impl_->x_ = rhs.p_impl_->x_; return *this; }
// the wrapping...
int X::get() const { return p_impl_->x_.get(); }
void X::operator=(int n) { p_impl_->x_ = n; }
}
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.
public
functions can be defined once in the .cpp if they refer to what would have beenprivate
members alap_impl_->xxx
. It's not necessarily the case that the implementation class needs to have member functions at all, though the more complicated the class the more likely it is that that's more convenient - it's also possible to e.g. passp_impl_
to a non-member function in an anonymous namespace if a "private" function is wanted. – Seductress