gpt4 book ai didi

c++ - 大数库的通用包装类

转载 作者:行者123 更新时间:2023-11-28 03:37:43 24 4
gpt4 key购买 nike

我正在从事的项目可以受益于在不同的大数字库之间轻松交换的可能性:GMP、OpenSSL 等。我当前的实现定义了一个模板抽象基类,我在其中实现了所有必需的运算符(只是为了有一些语法糖),我定义了所需的纯虚函数。

对于每个库,我都有一个这样的派生类:class BigNumberGmp : public BigNumberBase<BigNumberGmp> .我知道它有点破坏 OOP,但 C++ 协方差功能限制太多,它不允许我从方法返回 BigNumberBase 对象,仅返回引用,这是非常不受欢迎的...

问题是我希望使用我的自定义包装器的代码能够与任何此类包装器一起工作:BigNumberGmp、BigNumberOpenSsl 等。为了实现这一点,我定义了 typedef BigNumberGmp BigNumber 并将其放入一些条件宏中,像这样:

#if defined(_LIB_GMP)
typedef BigNumberGmp BigNumber;
#endif

另外,我以类似的方式包含了适当的 header 。此实现要求我在编译器选项中定义 _LIB_GMP 符号。

如您所见,这是一种相当老套的技术,我并不为此感到自豪。此外,它不会以任何方式隐藏专门的类(BigNumberGmp、BigNumberOpenSsl 等)。我也可以多次定义 BigNumber 类,包装在 _LIB_XXX 条件宏中,或者我可以在 BigNumber 类中多次实现所需的方法,对于每个库,也包装在 _LIB_XXX 条件宏中。后两个想法似乎比 typedef 实现更糟糕,它们肯定会弄乱 doxygen 输出,因为它无法弄清楚为什么我有多个同名项目。我想避免使用 doxygen 预处理器,因为我仍然依赖于 _LIB_XXX 定义...

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

最佳答案

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

选择使用哪个几乎与您拥有的一样(基于 #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;
}
};

现在我们可以定义一个通用父类(super class),其中包含使用这些易于映射的 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的struct,类型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 + 将来自父类(super class),您现在可以像这样使用它们:

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 的细节现在在模板特化中,但没有虚拟,也没有公共(public)父类(super class)。这意味着编译器可以内联包装器中的几乎所有内容,这将使它们基本上尽可能快。

由于特化,您还有可能访问所有可能使您的单元测试更易于编写/管理的实现(您也应该能够编写它们的模板化版本)。

如果您只希望其中之一可见,那么可以这样:

#if BIGNUM=="GMP"
typedef big_num<GMP> used_big_num;
#elif BIGNUM=="OpenSSL"
typedef big_num<OpenSSL> used_big_num;
#endif

如果 header 并非始终可用,您可能还需要对特化进行保护,在这种情况下,您需要一组 HAVE_GMP_BIGNUMHAVE_OPENSSL_BIGNUM 宏也是。

关于c++ - 大数库的通用包装类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10372879/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com