0

我正在从事的项目可能受益于能够在不同的大型库之间轻松交换: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 定义...

有没有我可以使用的优雅设计模式?你会如何处理这样的问题?

4

2 回答 2

1

我会在 pimpl 成语的帮助下做到这一点。我会首先定义一个包装类:

class BigNumber {
private:
    class BigNumber_impl {
        virtual void do_something() = 0;
    }
    BigNumber_impl * impl;
public:
    void do_something() {
        impl->do_something();
    }
};

然后我会让 BigNumberGMP 等从 BigNumber::BigNumber_impl 继承。然后,您可以返回 BigNumbers 的对象,但在底层仍然具有多态性。

这并不能解决创建 BigNumber 的问题,而且您还必须担心如何添加具有不同 BigNumber_impl 类型的 BigNumber。因此,您的原始解决方案可能适合您的目的。在某些时候,您将不得不依赖一些预处理器魔法。

于 2012-04-29T14:34:14.603 回答
1

看起来您每次切换库时都要重新编译,在这种情况下,您可以使用模板特化而不是继承。

选择使用哪个几乎是你所拥有的(#if基于某些东西),但你会保存虚拟成员,这意味着编译器仍然可以内联,这意味着在某些情况下它可能会更快。

首先,采用描述每个实现的结构。在这里,您可以将在所有库中以相同方式工作的基本 API 名称。例如,如果它们都支持加法操作,该操作采用指向两个大数字的指针并返回一个指向包含结果的新大数字的指针,您可以执行以下操作:

(请注意,我没有通过编译器运行它,我不知道实际的 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_BIGNUMHAVE_OPENSSL_BIGNUM宏。

于 2012-04-29T15:18:22.300 回答