Search code examples
c++abstractionforward-declarationtype-alias

Efficient and elegant interface abstraction


I'm trying to create a common interface for two third-party libraries that have similar functionality, so that I can code against the abstract interface and select at compile-time which implementation to use.

I need this abstract interface to not add any overhead, meaning polymorphism is out of the question. It shouldn't be needed anyway, since there's only one actual implementation in use. So my initial attempt looked like this:

AbstractInterface.h:

// Forward declarations of abstract types.
class TypeA;
class TypeB;
class TypeC;

TypeA *foo(TypeA *a, TypeB *b);
TypeB *bar(std::vector<TypeC*> &c);
TypeC *baz(TypeC *c, TypeA *c);

ImplementationOne.cpp:

class ActualTypeA {...};
using TypeA = ActualTypeA;   // Error!
...

Unfortunately this results in a compile error, saying that TypeA is being redefined using different types, even though the forward declaration wasn't telling it anything more than that it's a class. So the next thing I tried was this:

class TypeA : public ActualTypeA {};   // No more error
...
TypeA *foo(TypeA *a, TypeB *b)
{
    return actualFoo(a, b);   // Error
}

Here, actualFoo() returns an ActualTypeA*, which can't be automatically converted to a TypeA*. So I have to rewrite it into something like:

inline TypeA *A(ActualTypeA *a)
{
    return reinterpret_cast<TypeA*>(a);
}    

TypeA *foo(TypeA *a, TypeB *b)
{
    return A(actualFoo(a, b));
}

The reason I'm using the helper function A() is so that I don't accidentally cast something other than ActualTypeA* into TypeA*. Anyway, I'm not thrilled about this solution because my actual interface is tens of thousands of lines of code, per implementation. And all the A()'s, B()'s, C()'s etc. make it harder to read.

Furthermore, the implementation of bar() would need some additional voodoo:

inline std::vector<ActualTypeC*> &C(std::vector<TypeC*> &t)
{
    return reinterpret_cast<std::vector<ActualTypeC*>&>(t);
}

TypeB *bar(std::vector<TypeC*> &c)
{
    B(actualBar(C(c));
}

Another way to go about all this, that avoids requiring any implementation-side changes:

AbstractInterface.h:

class ActualTypeA;
class ActualTypeB;
class ActualTypeC;

namespace ImplemetationOne
{
    using TypeA = ActualTypeA;
    using TypeB = ActualTypeB;
    using TypeC = ActualTypeC;
}

class OtherActualTypeA;
class OtherActualTypeB;
class OtherActualTypeC;

namespace ImplemetationTwo
{
    using TypeA = OtherActualTypeA;
    using TypeB = OtherActualTypeB;
    using TypeC = OtherActualTypeC;
}

// Pre-define IMPLEMENTATION as ImplementationOne or ImplementationTwo
using TypeA = IMPLEMENTATION::TypeA;
using TypeB = IMPLEMENTATION::TypeB;
using TypeC = IMPLEMENTATION::TypeC;

TypeA *foo(TypeA *a, TypeB *b);
TypeB *bar(std::vector<TypeC*> &c);
TypeC *baz(TypeC *c, TypeA *c);

This has the issue that someone might accidentally use the implementation-specific types instead of the abstract ones. Also, it requires defining IMPLEMENTATION for every compilation unit that includes this header, and requires them to be consistent. I'd rather just compile either ImplementationOne.cpp or ImplementationTwo.cpp and that's it. Another disadvantage is that additional implementations would require modifying the header, even though we don't have an actual interest in the implementation-specific types.

This seems like a very common problem, so I'm wondering if I'm missing any solution that is more elegant and still efficient?


Solution

  • It doesn't look like C++ supports a way to define a forward-declared class as an existing class. So I ended up using the approach with casts (and helper functions) anyway. It became a 650 line change, but at least it guarantees not to add any overhead.

    I think I'll suggest to the C++ standards committee to add a language feature for this (or simply relax typedef/using to no produce a redefinition error) to make it easier in the future...