Search code examples
c++templatessmart-pointerspimpl-idiom

Templated classes with pimpl idiom incorrect


As described in the MSDN library here I wanted to experiment a bit with the pimpl idiom. Right now I have a Foo.hpp with

template<typename T>
class Foo {
public:
    typedef std::shared_ptr<Foo<T>> Ptr;

    Foo();
private:
    class Impl;
    std::unique_ptr<Impl> pImpl;
};

where the T parameter isn't used yet. The implementation is stored in Foo.cpp

template<typename T>
class Foo<T>::Impl {
public:
    int m_TestVar;
};

template<typename T>
Foo<T>::Foo() : pImpl(new Impl) {
    this->pImpl->m_TestVar = 0x3713;
}

Currently the compiler has two errors and one warning:

  • use of undefined type 'Foo<T>::Impl'; ... vc\include\memory in line 1150
  • can't delete an incomplete type; ... vc\include\memory in line 1151
  • deletion of pointer to incomplete type 'Foo<T>::Impl'; no destructor called; ... vc\include\memory in line 1152

What is the concflict here and how could I resolve it?

Edit. Removed the call to std::make_shared - copy&paste fail based on one old version.


Solution

  • I have had a similar issue - we've a base class in our system called NamedComponent and I wanted to create a template which takes an existing named component and converts it into a pimpl facade.

    What I did was separate the template into a header and an inline file, and create a function to cause the template to be instantiated. This allows the implementation to be in a library, with the template instantiations of the facade with that implementation, and for the client to be able to use the facade based on the template and a forward declaration of the implementation.

    header 'Foo.h':

    template<class T> class Foo
    {
    public:
        Foo ();
        virtual ~Foo();
    
    private:
        T *impl_;
    
    public:
        // forwarding functions
        void DoIt();
    };
    

    inline functions 'Foo.inl':

    #include "Foo.h"
    
    template<class T> Foo<T>::Foo() :
        impl_ ( new T )
    {
    }
    
    template<class T> Foo<T>::~Foo()
    {
        delete impl_;
    }
    
    // forwarding functions
    template<class T> void Foo<T>::DoIt()
    {
        impl_ -> DoIt();
    }    
    
    // force instantiation
    template<typename T>
    void InstantiateFoo()
    {
        Foo<T> foo;
        foo.DoIt();
    }
    

    implementation cpp file - include the template inline functions, define the implementation, reference the instantiation function:

    #include "Foo.inl"
    
    class ParticularImpl {
    public:
        void DoIt() {
            std::cout << __FUNCTION__ << std::endl;
        }
    };
    
    void InstantiateParticularFoo() {
        InstantiateFoo<ParticularImpl>();
    }
    

    client cpp file - include the template header, forward declare the implementation and use the pimpl facade:

    #include "Foo.h"
    class ParticularImpl;
    
    int main () {
        Foo<ParticularImpl> bar;
    
        bar.DoIt();
    }
    

    You may have to fiddle with the InstantiateFoo function's contents to force the compiler to instantiate all functions - in my case, the base called all the pimpl's functions in template methods so once one was referenced, they all were. You don't need to call the Instantiate functions, just link to them.