【发布时间】:2016-09-23 18:34:20
【问题描述】:
我正在尝试为两个具有相似功能的第三方库创建一个通用接口,以便我可以针对抽象接口进行编码并在编译时选择要使用的实现。
我需要这个抽象接口来不增加任何开销,这意味着多态性是不可能的。无论如何都不应该需要它,因为只有一个实际的实现在使用中。所以我最初的尝试是这样的:
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
}
这里,actualFoo() 返回一个 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,并要求它们保持一致。我宁愿只编译 ImplementationOne.cpp 或 ImplementationTwo.cpp,就是这样。另一个缺点是额外的实现需要修改标头,即使我们对特定于实现的类型没有真正的兴趣。
这似乎是一个非常常见的问题,所以我想知道我是否缺少任何更优雅且仍然有效的解决方案?
【问题讨论】:
-
您似乎正在努力解决继承本应解决的问题。由于您正在寻找编译时接口,因此您需要查看模板。
-
调用一层额外的函数是开销,所以它不能解决你的问题。简而言之,我怀疑您在没有分析或确定性的情况下选择了一些随机的东西作为“不要这样做它有成本”,而另一些则是“确定这样做它没有成本”。这两个接口真的是method for method,function for function,type for type bijective吗?
-
如果选择实现的信息可用,您可以在编译时使用 tag-dispatch 或 SFINAE 来选择实现。它是编译时多态性,但我想你只是想避免运行时多态性。
-
@Yakk 不,我目前用于实现接口的库不能完美地映射到它。基本上我在图书馆的顶部有一个薄包装。该层的成本是合理的。但现在我想使用一个备用库来实现相同的接口(使用抽象类型)。为这种抽象增加开销是不合理的,因为实现是在编译时选择的。
标签: c++ abstraction forward-declaration type-alias