我正在开发的项目可以从不同大数据库之间轻松交换的可能性中获益:GMP,OpenSSL等。我当前的实现定义了一个模板抽象基类,我在其中实现了所有必需的运算符(只是为了得到一些语法糖)我定义了所需的纯虚函数。
对于每个库,我都有一个这样的派生类:class BigNumberGmp : public BigNumberBase<BigNumberGmp>
。我知道它有点'打破OOP,但C ++协方差功能限制太多,它不允许我从方法返回BigNumberBase对象,只有引用,这是非常不受欢迎的......
问题在于我希望使用我的自定义包装器的代码能够使用任何这样的包装器:BigNumberGmp,BigNumberOpenSsl等。为了实现这一点,我定义了typedef BigNumberGmp BigNumber并将其置于一些条件宏中,像这样:
#if defined(_LIB_GMP)
typedef BigNumberGmp BigNumber;
#endif
此外,我以类似的方式包含适当的标题。此实现要求我在编译器选项中定义_LIB_GMP符号。
正如你所看到的,这是一种相当神圣的技巧,我并不为此感到骄傲。此外,它不会以任何方式隐藏专门的类(BigNumberGmp,BigNumberOpenSsl等)。我还可以多次定义BigNumber类,包含在_LIB_XXX条件宏中,或者我可以在BigNumber类中多次实现所需的方法,对于每个库,也包含在_LIB_XXX条件宏中。后两种想法似乎比typedef实现更糟糕,它们肯定会搞乱doxygen输出,因为它无法弄清楚为什么我有多个具有相同名称的项目。我想避免使用doxygen预处理器,因为我仍然依赖于_LIB_XXX定义......
我可以使用优雅的设计图案吗?你会怎么解决这个问题?
答案 0 :(得分:1)
我这样做的方法是在pimpl成语的帮助下。我首先要定义一个包装类:
class BigNumber {
private:
class BigNumber_impl {
virtual void do_something() = 0;
}
BigNumber_impl * impl;
public:
void do_something() {
impl->do_something();
}
};
然后我会从BigNumber :: BigNumber_impl继承BigNumberGMP等。然后,您可以返回BigNumbers的对象,但仍然具有多态性。
这不能解决创建BigNumbers的问题,您还必须担心如何使用不同的BigNumber_impl类型添加BigNumbers。因此,您的原始解决方案可能适合您的目的。在某些时候,你将不得不依赖一些预处理器魔法。
答案 1 :(得分:1)
看起来每次切换库时都要重新编译,在这种情况下,您可以使用模板专门化而不是继承。
选择使用哪个就像你拥有的那样(基于#if
的东西),但你要保存虚拟成员,这意味着编译器仍然可以内联,这意味着它可以在某些情况下显着更快
首先,采用描述每个实现的结构。在这里,您可以在所有库中以相同的方式放置基本API名称。例如,如果它们都支持一个add操作,它指向两个大的nums并返回一个指向包含结果的新的big num的指针,你可以这样做:
(请注意,我没有通过编译器运行它,我不知道实际的API是什么样的,但它应该足以让我们对该方法有一个大概的了解)
struct GMP {
GMP_ptr* add(GMP_ptr *l, GMP_ptr*r) {
return GMPadd(l, r);
}
};
struct OpenSSL {
OpenSSL_ptr* add(OpenSSL_ptr*, OpenSSL_ptr*) {
OpenSSL_ptr ret = NULL;
OpenSSLadd(l, r, &ret);
return ret;
}
};
现在我们可以定义一个包含这些易映射API的公共超类:
template< typename B, typename R >
class common {
public:
// Assume that all types have the same API
R operator + (const common &r) {
return R(B::add(l.ptr, r.ptr));
}
};
类型B是定义大数API的结构,类型R是真正的实现子类。通过像这样传入R,我们解决了共变量回归问题。
对于真正的实现,我们定义了一个可以为我们工作的模板:
template< typename B >
class big_num;
现在我们可以将其专门用于实现:
template<>
class big_num<OpenSSL> : common< OpenSSL, big_num<OpenSSL> > {
OpenSSL_ptr *ptr;
public:
big_num(OpenSSL_ptr*p)
: ptr(p) {
}
big_num(const char *s)
: ptr(OpenSSLBigNumFromString(s)) {
}
~big_num() {
OpenSSLBigNumFree(ptr)
}
};
operator +
将来自超类,您现在可以像这样使用它们:
void foo() {
big_num< GMP > gmp1("123233423"), gmp2("234");
big_num< GMP > gmp3 = gmp1 + gmp2;
big_num< OpenSSL > ossl1("1233434123"), ossl2("234");
big_num< OpenSSL > ossl3 = ossl1 + ossl2;
}
这里的优点是,由于使用结构来适应类似的API功能和一个模板中的通用实现,因此特化之间存在最少的代码重复。给定API的细节现在在模板特化中,但没有虚拟,也没有常见的超类。这意味着编译器可以在包装器中内联几乎所有内容,这将使它们基本上尽可能快。
由于专业化,您还可以访问所有可能使您的单元测试更容易编写/管理的实现(您应该能够编写那些的模板版本)。
如果您只想要其中一个可见,那么就像这样:
#if BIGNUM=="GMP"
typedef big_num<GMP> used_big_num;
#elif BIGNUM=="OpenSSL"
typedef big_num<OpenSSL> used_big_num;
#endif
如果标题并不总是可用,您可能还需要在专精项周围加上保护,在这种情况下,您还需要一组HAVE_GMP_BIGNUM
和HAVE_OPENSSL_BIGNUM
宏。