Public Member Functions | Related Functions | List of all members
aurora::PImpl< T, Size, Align > Class Template Reference

Fast PImpl idiom. More...

Public Member Functions

template<typename... Args>
 PImpl (Args &&...args)
 Construct implementation object, forwarding arguments.
 
 PImpl (const PImpl &origin)
 Copy constructor.
 
 PImpl (PImpl &&source)
 Move constructor.
 
PImploperator= (const PImpl &origin)
 Copy assignment operator.
 
PImploperator= (PImpl &&source)
 Move assignment operator.
 
 ~PImpl ()
 Destructor.
 
void swap (PImpl &other)
 Swaps two implementation objects.
 
T & operator* ()
 Returns the implementation object.
 
const T & operator* () const
 Returns the implementation object (const overload).
 
T * operator-> ()
 Returns the implementation object for member access.
 
const T * operator-> () const
 Returns the implementation object for member access (const overload).
 

Related Functions

(Note that these are not member functions.)

template<typename T , std::size_t Size, std::size_t Align>
void swap (PImpl< T, Size, Align > &lhs, PImpl< T, Size, Align > &rhs)
 Swaps two implementation objects.
 

Detailed Description

template<typename T, std::size_t Size, std::size_t Align>
class aurora::PImpl< T, Size, Align >

Fast PImpl idiom.

Enables the PImpl (pointer to implementation, aka. handle-body, compiler firewall) idiom without dynamic allocation. Storage for the implementation object is provided within the front-end object, but the type need not be complete at time of instantiation.

Template Parameters
TType of implementation object. Is incomplete at time of instantiation.
SizeStorage size large enough to hold a type T: Size >= sizeof(T)
AlignAlignment for type T: Align == alignof(T)

This class mimicks value semantics, not pointer semantics. In particular, the class invariant is that a PImpl<T, ...> object holds a fully constructed T object at any time. Its constructor forwards the arguments directly to T's constructor. Copy semantics and move semantics are provided, as long as the underlying type T supports them. The implementation object can be accessed using dereferencing operators.

Usage is completely safe, as the size and alignment are validated at compile time. In order to find out suitable values for Size and Align, you can instantiate a PImpl<T, 1, 0> object, which will always lead to a failed static assertion. When reading the compiler error, watch out for a correctSizeAndAlignWouldBe<S, A>() function instantiation, where S and A hold the correct values.

Keep in mind that using this technique comes with certain trade-offs. Since the implementation objects are constructed in-place, they increase the size of the front-end object. Its copy and move operations are thus more expensive than simple pointer copy. Constructors, destructors and assignment operators of the front-end class holding the PImpl instance as a member must be defined in a place where T is a complete type – usually the .cpp file that includes T's definition. So, you cannot use the compiler-generated special member functions, and it's necessary to define even empty destructors in the .cpp file. As the size and alignment of T must be hardcoded, additional maintenance may be necessary (but you cannot miss it because of static assertions). Furthermore, your code becomes less portable, because different compilers may use different sizes and alignments for the same type. Using PImpl instances in the front-end class makes revisions of the latter potentially binary incompatible, if the size or alignment of the implementation object changes. You can mitigate this issue by providing a larger size than necessary, at the expense of wasted memory.


The documentation for this class was generated from the following file: