Question

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.

Was it helpful?

Solution 4

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

OTHER TIPS

What you are looking for is a bridge design pattern

http://en.wikipedia.org/wiki/Bridge_pattern

It can be implemented using pimpl idiom.

Header file:

class IFoo {
public:
  virtual void doA() = 0;
  virtual void dob() = 0;
};

class Foo {
public:
  Foo();
  ~Foo();
  void doA() { impl->doA(); }
  void doB() { impl->doB(); }
private:
  IFoo* impl;
  // if needed - add clone to IFoo...
  Foo(const Foo&);
  Foo& operator = (const Foo&);
};

Somewhere else:

   class Vendor1Foo : public IFoo { ... }; 
   class Vendor2Foo : public IFoo { ... }; 

In .cpp file:

Foo::Foo() : impl(createFooImpl()) {}

To make it template ready for 40 classes:

template <class Interface>
Interface* createInterfaceFromConfig();

template <class Interface>
class ConcreteObject {
public:
   ConcreteObject() : impl(createInterfaceFromConfig<Interface>())
   Interface& interface() { return *impl; }
   const Interface& interface() const { return *impl; }
private:
   Interface* impl;
   // if needed - add clone to IFoo...
   ConcreteObject(const ConcreteObject&);
   ConcreteObject& operator = (const ConcreteObject&);
};

// example
class IFoo { ... };
typedef ConcreteObject<IFoo> Foo;

// somewhere else do specialization (.cpp file)

template <>
IFoo* createInterfaceFromConfig<IFoo>() { ... }

and specialization for other 39 interfaces...

I think, you're overcomplicating all this things. just use factory of Foos.

//accessible from client code:
struct IFoo
{
    virtual ~IFoo(){}
}

struct FooFactory
{
    IFoo* createFoo() const;

// you may only need pimpl here to fix _factory_ _interface_. 
// And you always have 1 factory
private:
    FooFactroyPrivate* d;
}

//implementation:
IFoo* FooFactory::createFoo() const
{
    //checking settings, creating implementation
}

Now, as far as you fixed the interface, you are free to add new implementations, since your clients get access only through the interface, you are free to change your implementation details.

What you are asking seems to me very similar to Dependency Injection. Some time ago I was looking for a DI framework for C++ and found pococapsule. I haven't used it in the end so I can't review it, but have a look.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top