#ifndef INCLUDE_GUARD_nlibImplWrapper_h #define INCLUDE_GUARD_nlibImplWrapper_h #include namespace nlib { /** @brief Wrapper for implementation details of a class (variant of PIMPL idiom) To hide implementation details of a class T, PIMPL (Private IMPLementation) idiom is often used. Implementation details are hiden in an object of class Impl that is forward declared, and a pointer to which becomes the only member of a class T. The drawback is that the Impl object is created on a heap in a T's constructor and must also be destroyed in T's destructor. ImplWrapper class template contains a buffer (data member of type char[]) that provides memory for the Impl object. Impl object is automatically constructed using the placement new operator in the constructor for ImplWrapper and destroyed with the explicit destructor call in the destructor for ImplWrapper. The size of the buffer must be explicitly specified in a template argument since the size of Impl is generally not known at the point of instantiation of ImplWrapper class template. Trying to use an object of class ImplWrapper with incorrect buffer size, results in a compilation error. If the size of Impl is not available when the template is instantiated, 0 can be specified. This will result in the Impl object being created on the heap instead of the buffer (a standard PIMPL idiom). @par Usage To hide details of a class, simply add a data member of class ImplWrapper. ImplWrapper overloads the operator ->, which provides direct access to members of Impl. Member function obj() can be used to access the Impl object itself. In addition, constructors and destructors of ImplWrapper provide automatic construction, destruction, copy-construction and assignment of Impl object. All members of class ImplWrappers are very simple and can be easily inlined. This means that using this class to hide implementation details provides little or no overhead compared to providing implementation details as a private pointer member in the original class. Sample use: @code class A { ... private: struct Impl; // Hidden implementation details. Must be // defined before accesseing m_impl (usually // in cpp file). ImplWrapper m_impl; // Object that stores and provides acces to // the actual implementation details. Replace // 8 with the actual size of Impl. }; @endcode @param TImpl Type that contains the class implementation details. This is normally an incomplete type (i.e. a forward declaration of a nested class Impl suffices). Note that the class must be fully defined before an ImplWrapper object can be constructed. Parameters can be passed to TImpl's constructor by using Par nested class. @param VImplSize Size (in bytes) of the TImpl class. Incorrect size results in compilation error. Specify 0 (default) to allocate Impl object on the heap. @test Function nlib::Test_ImplWrapper() performs an automatic test of this class template. */ template class ImplWrapper { public: /** Type that contains class implementation details. */ typedef TImpl Impl; /** Default constructor also default constructs the Impl object in the buffer or on the heap. */ ImplWrapper() { // Construct the Impl object in the buffer. new(&this->obj()) Impl; } /** @class Par @brief Provides a storage for parameters then can be passed to the Impl constructor. This class is used only by ImplWrapper constructor that takes a const reference to Par object as a parameter. @note Default implementation is not provided. This struct must be specialized for a particular Impl class if non-default Impl constructor is to be used. @sa ImplWrapper::ImplWrapper(const Par& p) */ struct Par; /** This constructor can be used to pass some parameters to the constructor of Impl object. Same as in the default constructor, the Impl object is constructed either in the buffer or on the heap. The following steps must be performed: -# Provide specialization of a nested Par class for a particular TImpl template parameter. This class will hold all parameters required by Impl constructor. -# Provide a constructor for Impl class that takes a Par object (const reference) as a parameter and uses its contents to construct the Impl object. -# Create temporary Par object and pass it to this constructor during member initialization. Example: @code class A { ... private: struct Impl; typedef ImplWrapper ImplWrapper; ImplWrapper m_impl; }; template<> struct A::ImplWrapper::Par { ... }; struct A::Impl { Impl(const A::ImplWrapper::Par& p) : ... { ... } ... }; A::A() : m_impl( A::ImplWrapper::Par(...) ) { ... } @endcode @sa Par */ explicit ImplWrapper(const Par& p) { // Construct the Impl object in the buffer. Parameters for Impl's // constructor are stored in Par object. new(this->m_buf) Impl(p); } /** Copy constructor also copies the Impl object. */ ImplWrapper(const ImplWrapper& o) { // Copy-construct Impl object in the buffer. new(this->m_buf) Impl(o.obj()); } /** ImplWrapper object can be explicitly constructed from Impl object. Use this constructor if Impl cannot be default constructed and a Par struct cannot be used to wrap parameters required by Impl's constructor. */ explicit ImplWrapper(const Impl& o) { // Copy-construct Impl object in the buffer. new(this->m_buf) Impl(o); } /** Destructor also destroys the Impl object in a buffer or deletes an object from the heap. */ ~ImplWrapper() { // This typedef triggers a compile time check of the buffer size. // Specifically, the below typedef is an error if template argument // VImplSize is not equal to sizeof(Impl). The error is reported // as: "Cannot allocate an array of size 0". In this case the value // of VImplSize template argument should be modified to match // sizeof(Impl). // Since destructor is instantiated as soon as an object of class // ImplWrapper is used, buffer size check is only performed here. typedef char ImplSizeCheckType [ VImplSize==sizeof(Impl) ]; // Explicitly call the destructor for Impl object that was // constructedin the buffer. this->obj().Impl::~Impl(); } /** Assignment operator also copies the Impl object. */ ImplWrapper& operator=(const ImplWrapper& o) { if(this!=&o) // Guard against self asignment { this->obj() = o.obj(); } return *this; } /** Convenient access to members of class Impl. */ Impl* operator->() { return &this->obj(); } /** Convenient access to members of class Impl for const objects */ const Impl* operator->() const { return &this->obj(); } /** Convenient access to members of class Impl for volatile objects */ volatile Impl* operator->() volatile { return &this->obj(); } /** Convenient access to members of class Impl for const volatile objects */ const volatile Impl* operator->() const volatile { return &this->obj(); } /** Return a reference to the Impl object stored in a buffer or on the heap. */ Impl& obj() { return *static_cast(static_cast(this->m_buf)); } /** Return a reference to the const Impl object stored in a buffer or on the heap. */ const Impl& obj() const { return *static_cast(static_cast(this->m_buf)); } /** Return a reference to the volatile Impl object stored in a buffer or on the heap. */ volatile Impl& obj() volatile { return *static_cast(static_cast(this->m_buf)); } /** Return a reference to the const volatile Impl object stored in a buffer or on the heap. */ const volatile Impl& obj() const volatile { return *static_cast(static_cast(this->m_buf)); } private: /** Memory for storing Impl object. */ // FIXME: // Check memory alignment! E.g. Impl can have pointer (and other) members, // which may have to be properly aligned to DWORD (or whatever) // boundaries. m_buf member does not have these restrictions. // Anonymous union with every possible member type would probably do the // trick, but would also increase the minimum size of ImplWrapper object. char m_buf[VImplSize]; }; /** @brief Specialization of ImplWrapper for unknown size of Impl class Impl object is stored on the heap. For member documentation please see main ImplWrapper template. */ template class ImplWrapper { public: typedef TImpl Impl; ImplWrapper() { // Construct Impl object on the heap. this->m_ptr = new Impl; } struct Par; explicit ImplWrapper(const Par& p) { // Construct Impl object on the heap. this->m_ptr = new Impl(p); } ImplWrapper(const ImplWrapper& o) { // Copy-construct Impl object on the heap. this->m_ptr = new Impl(o.obj()); } explicit ImplWrapper(const Impl& o) { // Copy-construct Impl object on the heap. this->m_ptr = new Impl(o); } ~ImplWrapper() { // Delete Impl object from the heap. delete this->m_ptr; this->m_ptr=0; } ImplWrapper& operator=(const ImplWrapper& o) { if(this!=&o) { this->obj() = o.obj(); } return *this; } Impl* operator->() { return &this->obj(); } const Impl* operator->() const { return &this->obj(); } volatile Impl* operator->() volatile { return &this->obj(); } const volatile Impl* operator->() const volatile { return &this->obj(); } Impl& obj() { return *this->m_ptr; } const Impl& obj() const { return *this->m_ptr; } volatile Impl& obj() volatile { return *this->m_ptr; } const volatile Impl& obj() const volatile { return *this->m_ptr; } private: /** Pointer to the Impl object on the heap. */ Impl* m_ptr; }; } // namespace #endif // #ifndef INCLUDE_GUARD_nlibImplWrapper_h