How to Write an Agile Pimpl in C++

Is it possible to write an agile Pimpl in c++?

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:

  1. do you have the X member function definition implement the logic, referring to p_impl_-> things all over the place, or

  2. 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.

pImpl idiom methods

Assuming I am interpreting your question correctly, i.e. that Foo is the public class and Bar is a public method. You have two options. Option A is:

void Foo::Bar()  // Foo is the public class
{
pImpl->baz = 5;
}

and Option B is:

void Foo::Bar() { pImpl->Bar(); }

where the Impl class contains:

void Impl::Bar() { baz = 5; }

I'm not sure if there are any published guidelines on choosing between the two. In my own code I use Option A for simple methods and Option B once it starts getting to the stage that it seems like there are pimples everywhere. It is a balance between redundant code and extra argument copying, and pimple abundance.

However, as suggested in comments, perhaps it would be cleaner to use Option B for everything, or at least every non-trivial function.

Constructor and destructor in c++ when using the pimpl idiom

This is an example of a correct way to implement PIMPL idiom in modern C++:

foo.hpp

#pragma once

#include <memory>

class Foo {
public:
Foo();
~Foo();

Foo(Foo const &) = delete;
Foo &operator=(Foo const &) = delete;

Foo(Foo &&) noexcept;
Foo &operator=(Foo &&) noexcept;

void bar();

private:
class impl;
std::unique_ptr<impl> pimpl_;
};

foo.cpp:

#include "foo.hpp"

#include <iostream>

class Foo::impl {
public:
impl() = default;
~impl() = default;

impl(impl const &) = default;
impl &operator=(impl const &) = default;

impl(impl &&) noexcept = default;
impl &operator=(impl &&) noexcept = default;

void bar() { std::cout << "bar" << std::endl; }
};

Foo::Foo() : pimpl_(new impl{}) {}
Foo::~Foo() = default;

Foo::Foo(Foo &&) noexcept = default;
Foo &Foo::operator=(Foo &&) noexcept = default;

void Foo::bar() { pimpl_->bar(); }

main.cpp:

#include "foo.hpp"

int main(int argc, char const *argv[]) {
Foo foo;
foo.bar();
return 0;
}

There are a few words that are to be said:

  • use a smart pointer (unique or shared) to hold reference(s) to 'impl' object. It'll help you like in JAVA control number of references to an underlying object. In this example, as just Foo class is gone 'impl' is automatically destroyed, there is no need to manually watch the lifetime of the 'impl' object
  • 'impl' MUST be always completely declared and defined in *.cpp file (or bunch of files) or internal *.hpp and/or *.cpp files so that a user of your front class (i.e., in this example 'Foo') that aggregates 'impl' is impossible to see any changes and content of your 'impl' class, that is why PIMPL idiom exists: to persist the interface of the front class for a user and all the changes mostly shall be done in the hidden 'impl' class
  • The destructor of the front class MUST be always defined in *.cpp file since the compiler must know how to destroy the 'impl' that is not seen thoroughly from the *.hpp file where a front class is declared
  • The special functions with rvalue-references (move-ctor and move-assignment operator) MUST also be defined in *.cpp file due to the same reason as in the previous clause

Is there any way to limit repetitive boilerplate when using the PIMPL idiom?

To close out this question: ultimately I think Adrian's comment addresses the issue the best: "I tend to implement the impl class's methods in the class definition, which reduces some of the repetition."

My code above would become:

// foo.cpp:
class foo::impl {
public:
// lots of private state, helper functions, etc.
};

foo::foo() : m_pimpl(new impl()) { }
foo::~foo() { delete m_pimpl; m_pimpl = NULL; }
void foo::bar(a b, c d) {
... code using m_pimpl-> when necessary ...
}
void foo::baz(a b, c d, e f, g h, i j) {
... code using m_pimpl-> when necessary ...
}
void foo::quux(a b, c d, e f, g h, i j) {
... code using m_pimpl-> when necessary ...
}

Now it's much more reasonable - just one for the declaration and one for the definition. There's the small overhead when converting a class to use pimpl of adding the m_pimpl->s, but IMO this is less annoying than having all the repetition.

C++ Pimpl Idiom Incomplete Type using std::unique_ptr

You can't use defaulted constructors and assignment operators (such as SomeInt( SomeInt&& other ) = default;) declared in header file with Pimpl classes, because the default implementations are inline, and at the point of declaration SomeInt's declaration SomeInt::impl is incomplete, so unique_ptr complains. You have to declare and define out of line (that is, in implementation file) all special member functions yourself.

That is, change SomeInt and SomeComposite declarations as follows:

// SomeInt.h
SomeInt( SomeInt&& other ); // move
SomeInt& operator=( SomeInt&& other ); // move assign

// SomeInt.cpp
// after definition of SomeInt::impl
SomeInt::SomeInt( SomeInt&& other ) = default;
SomeInt& operator=( SomeInt&& other ) = default;

Another option is to create your own Pimpl pointer, as suggested in this answer.

Why should the PIMPL idiom be used?

  • Because you want Purr() to be able to use private members of CatImpl. Cat::Purr() would not be allowed such an access without a friend declaration.
  • Because you then don't mix responsibilities: one class implements, one class forwards.

When to use Pimpl pattern over Nested class in C++ or vice versa?

You're mixing up several things:

  1. first example

    1. Type: opaque - that means the type name is visible to users of this header, but the definition is hidden.

      Opaque types are particularly useful when you want to share a pointer with your users, but not the details of what it points to (either to discourage users from fiddling with it, or to break up dependencies).

    2. Storage: pimpl. This just means the users know you (may) have an instance of the opaque type somewhere, and they know the size of your top-level object includes that pointer.

  2. second example

    1. Type: nested - this avoids polluting the enclosing namespace (so there can be other types of Node in the same namespace in your program, unlike the first example) but exposes all the details.

      Note that your nested type could also be forward-declared, to control both visibility and namespace pollution.

    2. Storage: missing. Did you mean to have a Node *m_impl here too? Otherwise the examples aren't directly comparable, as there's nowhere for your Graph to keep a Node, however the type is declared.



Related Topics



Leave a reply



Submit