Я пытаюсь создать общий интерфейс для двух сторонних библиотек с аналогичной функциональностью, чтобы я мог кодировать абстрактный интерфейс и выбирать во время компиляции, какую реализацию использовать.
Мне нужен этот абстрактный интерфейс, чтобы не добавлять никаких накладных расходов, то есть о полиморфизме не может быть и речи. В любом случае это не должно быть необходимо, поскольку используется только одна фактическая реализация. Итак, моя первоначальная попытка выглядела так:
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!
...
К сожалению, это приводит к ошибке компиляции, говорящей о том, что TypeA переопределяется с использованием других типов, хотя предварительное объявление не говорит ничего, кроме того, что это класс. Итак, следующее, что я попробовал, было следующим:
class TypeA : public ActualTypeA {}; // No more error
...
TypeA *foo(TypeA *a, TypeB *b)
{
return actualFoo(a, b); // Error
}
Здесь factFoo() возвращает ActualTypeA*, который не может быть автоматически преобразован в TypeA*. Поэтому я должен переписать его во что-то вроде:
inline TypeA *A(ActualTypeA *a)
{
return reinterpret_cast<TypeA*>(a);
}
TypeA *foo(TypeA *a, TypeB *b)
{
return A(actualFoo(a, b));
}
Причина, по которой я использую вспомогательную функцию A(), заключается в том, что я случайно не привожу что-то отличное от ActualTypeA* к TypeA*. В любом случае, я не в восторге от этого решения, потому что мой фактический интерфейс состоит из десятков тысяч строк кода для каждой реализации. И все A(), B(), C() и т. д. затрудняют чтение.
Кроме того, реализация bar() потребует некоторого дополнительного колдовства:
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));
}
Еще один способ обо всем этом, который позволяет избежать каких-либо изменений на стороне реализации:
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);
Это связано с тем, что кто-то может случайно использовать типы, специфичные для реализации, вместо абстрактных. Кроме того, это требует определения IMPLEMENTATION для каждой единицы компиляции, которая включает этот заголовок, и требует их согласованности. Я бы предпочел просто скомпилировать либо РеализацияOne.cpp, либо РеализацияТво.cpp, и все. Другим недостатком является то, что дополнительные реализации потребуют изменения заголовка, даже если нас не интересуют типы, специфичные для реализации.
Это кажется очень распространенной проблемой, поэтому мне интересно, не упустил ли я какое-либо решение, которое было бы более элегантным и все еще эффективным?