5

Sorry for the long and confusing title.

I have a class header file like this

#pragma once
#include <thread>
#include <boost/asio.hpp>
#include <another3rdpartylib/doodads.h>

class A {
public:
  A();
  Method1();
  Method2();
private:
  std::thread thread;
  boost::asio::socket socket;
  another3dpartylib::doodad gizmo;
}

Now the users of the class don't and shouldn't care about the private parts. How can I allow the users to include the class without dragging <thread>, <boost/asio.hpp> and <another3rdpartylib/doodads.h>?

Technically the only thing the users should care about is sizeof(A). Am I mistaken?

TheTeaMan
  • 2,373
  • 3
  • 15
  • 13
  • 4
    Search for the "pimpl idiom". – juanchopanza Dec 27 '13 at 17:01
  • 1
    You need more information than `sizeof(A)`. For instance, you also need the alignment of each non static data member. In addition, the compiler needs to know whether destructors are virtual, members are copy/move constructible/assignable, etc. Unless, you use the "pimpl idiom" as said by others. – Cassio Neri Dec 27 '13 at 17:04
  • Slightly changing @juanchopanza's answer: you can make `thread`, `socket`, and `gizmo` pointers. Then you'll be able to hide all the implementation details (with reference to all the header files) in the `.cpp` file. – Dmitry Markin Dec 27 '13 at 17:05

2 Answers2

8

The common way in C++ to split the interface and implementation of a class is to use the Pointer To Implementation (PIMPL) Idiom.

The PIMPL idiom encapsulates the implementation of a class by storing a reference/pointer to a class which is in charge of doing the things, only offering the user a class, the interface, which only acts as a wrapper of the implementation class.

For example: Consider a library which implements a extremely fast stack for floating-point operations. The interface of the container is very simple:

class fast_stack
{
public:
    void push( float );
    float pop();
};

But as this library implements a extremely fast stack, its implementation is based on extremely complicated libraries, inline assembly, brainfuck interoperability etc.

The user of that library only want's a stack, not a horrible bunch of code, libraries, and dependencies. How we could hide all of that scream and only provide him a simple and clean interface? Thats where the PIMPL kicks in:

//stack_inferno.hpp

#include <thread>
#include <Boost/ASIO>
... More monters here

class fast_stack_infernal_implementation
{
   std::thread* _mptr_thread_lcl;
   float******* _suicide_cache_memory_pool;
   ... etc etc

   void push( float elem )
   {
        //Please don't see this code, it could hurt your eyes
   }

   float pop()
   {
        // Same as above
   }
};



//fast_stack.hpp (Revisited)

class fast_stack_infernal_implementation; //Note the forward declaration. 

class fast_stack
{
public:
    void push( float );
    float pop();

private:
    std::unique_ptr<fast_stack_infernal_implementation> implm;
};



//fast_stack.cpp

#include "stack_inferno.hpp" //Tah daah!


fast_stack::fast_stack() : impl( new fast_stack_infernal_implementation() )
{
}

void fast_stack::push( float elem )
{
     implm->push( elem );
}

float fast_stack::push()
{
     return implm->pop();
}

As you can see, the PIMPL idiom has many advantages:

  • Hides the complexity of an implementation to the user, and provides only the interface.
  • The user header file (The interface header file) does not include the implementation header, so a change on the implementation does not result in a recompilation of the hole library, and the user code. This is a very important point for a library, because changes on the library not always breaks the user code.
Manu343726
  • 13,969
  • 4
  • 40
  • 75
  • 5
    @CaptainObvlious Actually, whenever I've seen this acronym spelled out, if was "pointer to implementation." Can you provide a supporting quote? – Angew is no longer proud of SO Dec 27 '13 at 17:06
  • @Angew When I'm following all of the links I'm landing on the original [wiki page](http://c2.com/cgi/wiki?PimplIdiom), which refers it as the **p** rivate **impl** ementation pattern. – πάντα ῥεῖ Dec 27 '13 at 17:13
  • Where is the PIMPL in your example? Can you please elaborate? – TheTeaMan Dec 27 '13 at 17:33
  • @TheTeaMan sorry, I was writting the example – Manu343726 Dec 27 '13 at 17:35
  • You can also use a private `struct` member as the implementation. E.g. `class fast_stack { private: struct impl; std::unique_ptr pimpl; public: void push(float); float pop(); };` – dyp Dec 27 '13 at 17:42
  • Note: It's important to have both ctor and dtor of `fast_stack` non-inlined, which implies *not* using the implicitly declared versions. (For the dtor: the `unique_ptr` needs access to the dtor of `fast_stack_infernal_implementation`) – dyp Dec 27 '13 at 17:44
  • 1
    @DyP Of course. I have written the example on this way to split clearly the implementation and the interface. But I personally prefer the nested class, which can't be accessed by the user in any way. – Manu343726 Dec 27 '13 at 17:45
  • @DyP I haven't considered that. Why is inlining (Not inlining) important? – Manu343726 Dec 27 '13 at 17:47
  • 1
    The ctor and dtor can only be defined in a TU where the definition of `fast_stack_infernal_implementation` is available. If they were `inline`, you would need a definition of the ctor and dtor in every TU in which they're called. – dyp Dec 27 '13 at 17:48
  • @DyP mmm I haven't considered problems between the forward declaration and the different translation units. Thanks. The only way to get rid of this is to break inlining explicitly declaring the dtor and ctor? – Manu343726 Dec 27 '13 at 17:53
  • The ctor cannot be inlined easily, there needs at one point to be a function call or `new`-expression that returns a `fast_stack_infernal_implementation*` and this creation cannot appear in every TU. For the dtor, a custom deleter or a `std::shared_ptr` are also possible (the `shared_ptr` creates a "deleter" in its ctor, see http://stackoverflow.com/questions/3899790/shared-ptr-magic). – dyp Dec 27 '13 at 17:59
2

As answered, the basic idea is to isolate the dependencies solely in the .cpp file. This has several implications:

  1. The class size needs not depend on the implementation; a common trick is to use a pointer to a dynamically allocated structure but you can also just reserve raw memory within the class.
  2. Since the class definition does not precise what it is composed of, no method depending on the exact innards of the class may be inlined in the header. This is a penalty one should be aware of.
  3. A benefit of this, though, is that one can maintain ABI compatibility when changing the (hidden) attributes (to some degree).

So, how to apply it ?


The bona-fide PIMPL (templatized):

template <typename T>
class pimpl_internal_interface {
public:
    virtual ~pimpl_internal_interface() {}

    virtual std::unique_ptr<pimpl_internal_interface> clone() const = 0;

    virtual T& get() = 0;
    virtual T const& get() const = 0;
}; // class pimpl_internal_interface

template <typename T>
class pimpl_internal: public pimpl_internal_interface
public:
    template <typename... Args>
    pimpl_internal(Args&&... args): _impl(std::forward<Args>(args)...) {}

    virtual std::unique_ptr<pimpl_internal_interface> clone() const {
        return std::make_unique<pimpl_internal>(_impl);
    }

    virtual T& get() { return _impl; }
    virtual T const& get() const { return _impl; }

private:
    T _impl;
}; // class pimpl_internal

template <typename T>
class pimpl {
public:
    template <typename... Args>
    pimpl(Args&&... args):
       _impl(std::make_unique<pimpl_internal<T>>{std::forward<Args>(args)...}) {}

    pimpl(pimpl&& other) = default;

    pimpl(pimpl const& other): _impl(other->clone()) {}

    pimpl& operator=(pimpl other) {
        std::swap(_impl, other._impl);
        return *this;
    }

    T& get() { return _impl->get(); }
    T const& get() const { return _impl->get(); }
private:
    std::unique_ptr<pimpl_internal_interface<T>> _impl;
}; // class pimpl

Can now be used:

// Complicated.hpp
#pragma once
#include <utils/pimpl.hpp>

class Complicated {
public:
    Complicated();

    void doit();

private:
    struct Impl;
    pimpl<Impl> _;
};

// Complicated.cpp
#include "Complicated.hpp"

// other includes

struct Complicated::Impl {
    // gross stuff you'd rather hide
}; // struct Complicated::Impl

Complicated::Complicated(): _(/*arguments*/) {}

void Complicated::doit() {
    Impl& impl = _.get();
    // use impl
};

The net disadvantage is that it requires a separate dynamic allocation (managed by std::unique_ptr), however it is relatively simple otherwise.


Alternatives ? A common, though a tad more complicated, alternative is to allocate the required memory inline (within the object) to avoid any dynamic allocation. Of course this requires explicitly specifying how much memory is necessary.

template <typename T>
class pimpl_internal_interface {
public:
    virtual void copy(T* dst, T const* src) const = 0;
    virtual void move(T* dst, T* src) const = 0;

    virtual void copy_assign(T* dst, T const* src) const = 0;
    virtual void move_assign(T* dst, T* src) const = 0;

    virtual void destroy(T* t) const = 0;

protected:
    ~pimpl_internal_interface() {}
}; // class pimpl_internal_interface

template <typename T>
class pimpl_internal final: public pimpl_internal_interface<T> {
public:
    virtual void copy(T* dst, T const* src) const override {
        new (dst) T{*src};
    }

    virtual void move(T* dst, T* src) const override {
        new (dst) T{std::move(*src)};
    }

    virtual void copy_assign(T* dst, T const* src) const override {
        *dst = *src;
    }

    virtual void move_assign(T* dst, T* src) const override  {
        *dst = std::move(*src);
    }

    virtual void destroy(T* t) const override {
        t.~T();
    }
}; // class pimpl_internal

template <typename T, size_t Size, size_t Alignment = alignof(void*)>
class pimpl {
public:
    template <typename... Args>
    pimpl(Args&&... args) {
        static_assert(Size >= sizeof(T), "Review Size!");
        static_assert(Alignment >= alignof(T), "Review Alignment!");

        static pimpl_internal<T> const I;
        interface = &I;

        new (&storage) T{std::forward<Args>(args)...};
    }

    pimpl(pimpl&& other): interface(other.interface) {
        interface->move(this->pointer(), other->pointer());
    }

    pimpl(pimpl const& other): interface(other.interface) {
        interface->copy(this->pointer(), other->pointer());
    }

    pimpl& operator=(pimpl&& other) {
        interface->move_assign(this->pointer(), other->pointer());
        return *this;
    }

    pimpl& operator=(pimpl const& other) {
        interface->copy_assign(this->pointer(), other->pointer());
        return *this;
    }

    ~pimpl() { interface->destroy(this->pointer()); }

    T& get() { return *this->pointer(); }
    T const& get() const { return *this->pointer(); }

private:
    using Storage = std::aligned_storage<Size, Alignment>::type;

    T* pointer() { return reinterpret_cast<T*>(&storage); }
    T const* pointer() const { return reinterpret_cast<T const*>(&storage); }

    pimpl_internal_interface<T> const* interface;
    Storage storage;
}; // class pimpl

And using it is similar to the bona-fide version, albeit with an explicit size:

class Complicated {
public:
    Complicated();

    void doit();

private:
    struct Inner;
    pimpl<Inner, 32> _; // reserve 32 bytes for Inner
}; // class Complicated
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722