Search code examples
c++qtinterfacefactory-patternpimpl-idiom

Pimpl idiom pointing to configurable implemenation


I've read that Pimpl is good for binary compatibility and interfaces are good for being able to easily switch out implementation. I need to combine both of these techniques to allow my application to be able to switch the underlying implementation out via a config file.

Here is how my current design is laid out:

class Foo: provides the client facing API, I'm concerned with ABI compatibility here
class IFoo: interface class (all pure virtual methods, virtual dtor)
class Vendor1Foo: implements IFoo, using Vendor1's library
class Vendor2Foo: implements IFoo, using Vendor2's library

By not using pimpl and strictly using interfaces, client code might look like:

IFoo* foo = new Vendor1Foo();

The issue is that my client code cannot know about Vendor1 or Vendor2 at all and Foo is just one of many classes that I have to do this for.

The over all concept of what I'm trying to do is the following:

class foo
{
  private:
  QScopedPointer<IFoo> pimpl;
  void initImpl();  // Reads from QSettings and initializes pimpl
}

Any ideas for an elegant solution to this problem?

I'm hoping to come up with some macros or a template class/method to help standardize on how I deal with this and minimize violating DRY.

The template class might serve as a pimpl helper like Herb Sutter's take on a generalized pimpl idiom for C++11: herbsutter.com/gotw/_101 and it would also have to contain the logic for instantiating the correct implementation depending on the configuration

There are elements of the pimpl idiom, the bridge pattern, and factory pattern here. In my above example initImpl() can be thought of as a factory method. I'm looking for a solution that may or may not use all of these patterns.

I have already viewed c++ pimpl idiom : Implementation depending on a template parameter as well as most of the pimpl idiom questions on SO. The title seemed promising, but it didn't help with my particular use case.

I cannot use C++11 and am using Qt. D-Pointers do not solve my problem as they are bound to a single implementation.


Solution

  • This solution has actually worked out for me, so I'm placing it here as an answer:

    PimpleHelper.h is a Pimpl helper class to reduce boiler plate code. Uses the VendorFactory to instantiate the correct vendor implementation. Multiple vendors will register the fact that they implement a given interface; only one vendor's implementation is ever instantiated for a given interface.

        #include <QSettings>
        #include "VendorFactory.h"
        #include <cxxabi.h>
    
        // Pimpl Helper
        template<typename T>
        class PimplHelper
        {
            public:
            PimplHelper()
            {
                m_interfaceNameImplemented = demangle(typeid(T).name());
                initializeImpl();
            }
    
            T* getImpl()
            {
                return theImpl.data();
            }
    
            private:
            QScopedPointer< T > theImpl;
            QString m_interfaceNameImplemented;
    
            void initializeImpl()
            {
    
                // Read in configuration
                QSettings settings("AppSettings.ini", QSettings::IniFormat);
                QString vendorToUse = settings.value("VENDOR_IMPLEMENTATION_KEY", "Vendor1").toString();
    
                qDebug() << "Vendor to use is: " << vendorToUse << " Interface Implemented: " << m_interfaceNameImplemented;
    
    
                // Obtain an instance of the vendor's class that implements the T interface
                theImpl.reset(
                                VendorFactory<T>::create(vendorToUse, m_interfaceNameImplemented)
                             );
    
                if(!theImpl)
                    qDebug() << "PimplHelper::initializeImpl, error resolving implementation for: "
                             << vendorToUse << " Interface Implemented: " << m_interfaceNameImplemented;
            }
    
            const QString demangle(const char* name)
            {
                int status = -4;
                char* res = abi::__cxa_demangle(name, NULL, NULL, &status);
                const char* const demangled_name = (status==0)?res:name;
                QString ret_val(demangled_name);
                free(res);
                return ret_val;
            }
        };
    

    VendorFactory.h creates instances of classes implemented by various vendors. Vendors register their implementations with the factory via a macro.

        #include <QtCore>
    
        template< class T>
        class VendorFactory
        {
            private:
            typedef T* (*CreateFunc)();
            typedef QMap<QString, CreateFunc> FunctionMap;
    
            public:
            static T * create(const QString& vendorName, const QString& interfaceName)
            {
                typename FunctionMap::iterator it = creators()->find(vendorName + interfaceName);
                if (it == creators()->end())
                    return NULL;
                return (it.value())();
            }
    
            static bool reg(const QString& vendorName, const QString& interfaceName, CreateFunc fun)
            {
                qDebug() << "Registering: " << vendorName + interfaceName << endl;
                creators()->insert(vendorName + interfaceName, fun);
                return true;
            }
    
            static FunctionMap * creators()
            {
                static FunctionMap* creators = new FunctionMap;
                return creators;
            }
    
            virtual ~VendorFactory() {}
    
        };
    
    
        /// @brief This registers a Vendor's class in the factory and adds a factory function named create_vendorImplClass()
        /// and calls VendorFactory::reg() by the help of a dummy static variable to register the function.
        /// @param vendorName A string representing the vendor's name
        /// @param vendorImplClass The class implementing the interface given by the last parameter
        /// @param interface The  interface implemented by the vendorImplClass
        #define REGISTER_IN_FACTORY( vendorName, vendorImplClass, interface ) \
            namespace { \
            interface* create_ ## vendorImplClass() {  return new vendorImplClass; } \
            static bool vendorImplClass ## _creator_registered = VendorFactory< interface >::reg( vendorName, # interface, create_ ## vendorImplClass); }
    

    And here is how they are used:

    Person.h (public-facing API)

    #include "IPerson.h"
    #include "PimplHelper.h"
    
    // Public facing API
    class Person: public IPerson
    {
        public:
    
        Person()
        {
            impl.reset( new PimplHelper<IPerson>());
        }
    
        QString GetFirstName();   
        QString GetLastName();
    
        private:
        QScopedPointer< PimplHelper<IPerson> > impl;
    };
    

    Person.cpp (public-facing API)

    #include "Person.h"
    
    
    QString Person::GetFirstName()
    {   // I'd like to remove the call to getImpl() here
        // and just use the overloaded -> operator, but it
        // gives me a "has no member named GetFirstName()" error
        return impl->getImpl()->GetFirstName();
    }
    
    QString Person::GetLastName()
    {
        return impl->getImpl()->GetLastName();
    }
    

    PersonImpl1.h contains Vendor1's implementation

    #include "IPerson.h"
    #include "VendorFactory.h"
    
    
    // Private Implementation
    class PersonImpl1: public IPerson
    {
    
        public:
    
        PersonImpl1():
            FirstName("Jon"), LastName("Skeet")
        {}
    
        QString GetFirstName()
        {
            return FirstName;
        }
    
        QString GetLastName()
        {
            return LastName;
        }
    
        private:
        QString FirstName;
        QString LastName;
    
    };
    
    REGISTER_IN_FACTORY("Vendor1", PersonImpl1, IPerson)
    

    PersonImpl2.h contains Vendor2's implementation

    #include "IPerson.h"
    #include "VendorFactory.h"
    
    // Private Implementation
    class PersonImpl2: public IPerson
    {
    
        public:
    
        PersonImpl2(): FirstName("Chuck"), LastName("Norris")
        {}
    
        QString GetFirstName()
        {
            return FirstName;
        }
    
        QString GetLastName()
        {
            return LastName;
        }
    
        private:
        QString FirstName;
        QString LastName;
    };
    
    REGISTER_IN_FACTORY("Vendor2", PersonImpl2, IPerson)
    

    Finally, the main.cpp file:

    #include <QCoreApplication>
    #include <QDebug>
    
    #include "Person.h"
    
    // The following needs to be included for the static/auto registration
    // with the VendorFactory to occur. I'm not exactly sure why.
    #include "PersonImpl1.h"
    #include "PersonImpl2.h"
    
    int main(int argc, char *argv[])
    {
        Q_UNUSED(argc)
        Q_UNUSED(argv)
    
        Person* p = new Person();
    
        qDebug() << "The person implemented is: "
                 << p->GetFirstName() << " " << p->GetLastName();
    
        qDebug() << "exiting";
    }
    

    Here is a list of other SO questions that helped me so far:

    Instantiate class from name?
    Dynamically register constructor methods in an AbstractFactory at compile time using C++ templates
    Register an object creator in object factory
    Entity/Component Systems in C++, How do I discover types and construct components?
    Is there a way to instantiate objects from a string holding their class name?
    Static variable not initialized
    Unmangling the result of std::type_info::name