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など)を非表示にすることもありません。_LIB_XXX条件付きマクロでラップされたBigNumberクラスを複数回定義することも、_LIB_XXX条件付きマクロでラップされたライブラリごとにBigNumberクラス内に必要なメソッドを複数回実装することもできます。後者の2つのアイデアは、typedefの実装よりもさらに悪いように見え、同じ名前のアイテムが複数ある理由を理解できないため、doxygenの出力を確実に台無しにします。私はまだ_LIB_XXXの定義に依存しているので、doxygenプリプロセッサの使用を避けたいです...

代わりに使用できるエレガントなデザインパターンはありますか?そのような問題にどのように取り組みますか?

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名を配置できます。たとえば、それらがすべて2つの大きなnumへのポインターを受け取り、結果を含む新しい大きなnumへのポインターを返すadd操作をサポートしている場合は、次のように実行できます。

(私はこれをコンパイラーで実行していないことに注意してください。実際の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機能と1つのテンプレートの共通の実装を適応させるため、スペシャライゼーション間のコードの重複が最小限に抑えられることです。特定のAPIの詳細は現在、テンプレートスペシャライゼーションにありますが、仮想はなく、一般的なスーパークラスもありません。これは、コンパイラーがラッパー内のほとんどすべてをインライン化できることを意味します。これにより、本質的に可能な限り高速になります。

特殊化されているため、すべての実装にアクセスできる可能性があり、単体テストの作成/管理がはるかに簡単になります(それらのテンプレートバージョンも作成できるはずです)。

それらの1つだけを表示したい場合は、次のようにします。

#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 に答える