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?
The primary reason this is needed is that any code that uses a class needs to know about private class members in order to generate code that can handle it.
Public headers are those needed by the users of the library. Private headers are those needed to compile the library, but which are not needed by library users.
Private: The class members declared as private can be accessed only by the functions inside the class. They are not allowed to be accessed directly by any object or function outside the class. Only the member functions or the friend functions are allowed to access the private data members of a class.
Private members of a class are still members of the class, so they must be declared, as the implementation of other public members might depend on that private method. Declaring them will allow the compiler to understand a call to that function as a member function call.
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:
As answered, the basic idea is to isolate the dependencies solely in the .cpp
file. This has several implications:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With