6

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.

Community
  • 1
  • 1
Chris Andrews
  • 1,881
  • 3
  • 21
  • 31
  • What you're suggesting is very similar to the recommendations Herb Sutter has for interfaces: http://www.gotw.ca/publications/mill18.htm – Mark Ransom Oct 15 '12 at 19:15
  • @Mark I can't bring up that site here at work. Are you talking about the NVI idiom? How might it be applied to solve my problem? Thanks. – Chris Andrews Oct 15 '12 at 20:34
  • Yes, it's the NVI idiom. It probably doesn't help you, especially if you're already familiar with it. It's just not very often that I see the interface defined with a concrete class and I thought I'd bring it up. – Mark Ransom Oct 15 '12 at 20:47
  • If I was concerned about compatibility I certainly would not want to rely on names returned by `type_info::name`. – Paul Groke Oct 24 '12 at 21:54

4 Answers4

1

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...

PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • Well, it's a combination of the bridge pattern and the pimpl idiom. Simply saying the design pattern is not a solution. The bridge pattern does not solve the ABI compatibility issue and does not shield my clients from being exposed to the vendor specific implementation. – Chris Andrews Oct 15 '12 at 19:12
  • @ChrisAndrews See my example implementation - client is not aware of implementation which is placed "somewhere else" and the implementation of `Foo::Foo()` is also not exposed. What you mean by ABI compatibility - do you mean to copy objects - or to serialize them? – PiotrNycz Oct 15 '12 at 19:19
  • Okay, I must of posted the comment while you were creating the example, which does use pimpl. So now, how do you templatize this so it can be applied to many classes (e.g. 40 classes). The Issue is that template implementation must be done in the header file, which couples clients to the implementation. I'm not above using macros for this type of thing if it will solve the problem with minimal code. – Chris Andrews Oct 15 '12 at 19:39
  • Yes, the template class might serve as a pimpl helper like Herb Sutter's take on a generalized pimpl idiom for C++11: http://herbsutter.com/gotw/_101 (I can't use C++11 though) and it would also have to contain the logic for instantiating the correct implementation depending on the configuration. – Chris Andrews Oct 15 '12 at 19:56
  • I did not read this article - but provided template for this. Of course you have to define 40 interfaces and 40xN implementation where N is number of different vendors libraries. – PiotrNycz Oct 15 '12 at 19:58
  • To clarify I'm talking about only 2 or 3 vendor implementations each of which contain 40 or so classes for each library. What if all 40 classes followed the same naming convention, e.g. VendorNameClassName and what if they all of default constructors???? I don't see why this couldn't be completely automated. I'd just like to decorate my client facing API classes and be done. – Chris Andrews Oct 15 '12 at 20:30
  • It is not about class names. Important is these classes interfaces - I mean what public functions they have. With my proposal you need to define interfaces to make unified access to your 2 or 3 libraries. These interface must be defined by hand. These interfaces must be implemented for each library - another 80 or 120 classes. Rest you can do with my templates. There is no other way. You can make some improvement in these interfaces/classes definition - by defining common base parts of these interfaces. And some macros to define similar functions. That's all I can advice. – PiotrNycz Oct 15 '12 at 20:49
0

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.

Lol4t0
  • 12,444
  • 4
  • 29
  • 65
0

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.

davka
  • 13,974
  • 11
  • 61
  • 86
  • 1
    Yes, and I've come to the conclusion that there are no good DI frameworks for C++. pococapsule is a dead project as there hasn't been any commits in years. I did think about writing my own DI container at one point in time as I was inspired by http://www.codinginlondon.com/2009/05/cheap-ioc-in-native-c.html – Chris Andrews Oct 16 '12 at 17:27
0

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

Community
  • 1
  • 1
Chris Andrews
  • 1,881
  • 3
  • 21
  • 31