2

固定サイズのデータ​​を格納するクラスがあります

template<size_t SIZE> class Data {...};

現在、データを生成するためのさまざまなアルゴリズムがあります (疑似乱数ジェネレーターなど)。

class PseudoRandom1 {
   template<size_t SIZE> Data<SIZE> generate();
};

class PseudoRandom2 {
   template<size_t SIZE> Data<SIZE> generate();
};

ここで、これらのジェネレーターのどれを呼び出すかについて、実行時に動的に決定したいと考えています。仮想テンプレートを使用すると (これは不可能です)、次のようになります。

class Generator {
  virtual template<size_t SIZE> Data<SIZE> generate() = 0;
};

class PseudoRandomX : public Generator {
  template<size_t SIZE> Data<SIZE> generate() override {...}
};

残念ながら、データ クラスの SIZE パラメータを非テンプレート ランタイム パラメータに変更することはできません。さらに、ユーザーがジェネレーター アルゴリズムを選択できるため、実際のジェネレーター インスタンスを実行時に決定する必要があります。可能であれば、タイプセーフなソリューション (boost::any なし) を希望します。

仮想テンプレートが使用できないことはわかっています。これを解決する別の方法はありますか?

4

4 に答える 4

2

以下は、一連のジェネレーターを実行時に動的に登録する CRTP を使用した例です (c-startup)。シングルトン ファクトリ パターンは、実装で定義された選択されたアルゴリズムに従って、実行時にデータの新しいインスタンスを動的に作成するために使用されます。

設計は 2 つの部分 (namespace util) に分割されます。最初の部分は、PseudoRandomGenerator Base と Template クラス、およびすべてのジェネレーターの Factory で構成されます。2 番目の部分は、さまざまなデータ生成アルゴリズムを実装するためのものです。2 番目の部分の各実装クラスは、個別のファイル (この場合は 3 つ) に分割できます。

実施例1

#include <iostream>
#include <string>
#include <map>
#include <memory>

namespace PseudoRandomGeneratorTypes { enum : int { Type1, Type2, Type3 }; }

namespace util {

    template<size_t SIZE>
    struct __Data {
        int a;
    };

    using Data = __Data<10>;

    class PseudoRandomGenerator {
    protected:
        PseudoRandomGenerator() {}

    public:
        auto getType() const { return _type; }
        virtual Data generate() const = 0;
    protected:
        int _type;
    };

    template<int PRGType, typename PRGImpl>
    class PRGTmpl : public PseudoRandomGenerator {

    public:

        static PseudoRandomGenerator* CreatePtr() {
            return new PRGImpl();
        }

        static const int TYPE;

    protected:
        PRGTmpl() { _type = TYPE; }

    };

    class PseudoRandomGeneratorFactory {
    public:
        typedef PseudoRandomGenerator* (*psg)();

        static auto get()
        {
            static PseudoRandomGeneratorFactory fact;
            return &fact;
        }

        auto Register(int id, psg m)
        {
            _map[id] = m;
            return id;
        }

        auto Create(int id)
        {
            return _map[id]();
        }

    private:
        PseudoRandomGeneratorFactory() {}
        ~PseudoRandomGeneratorFactory() {}

        std::map<int, psg> _map;

    };

    template <int arbitaryPRGType, typename arbitaryPRGImpl>
    const int PRGTmpl<arbitaryPRGType, arbitaryPRGImpl>::TYPE = PseudoRandomGeneratorFactory::get()->Register(arbitaryPRGType, &PRGTmpl<arbitaryPRGType, arbitaryPRGImpl>::CreatePtr);

}

namespace util {

    class PRGType1 : public PRGTmpl < PseudoRandomGeneratorTypes::Type1, PRGType1 > {
    public:
        virtual Data generate() const override final { return Data{ 111 }; }
    };
    template class PRGTmpl < PseudoRandomGeneratorTypes::Type1, PRGType1 >;

    class PRGType2: public PRGTmpl < PseudoRandomGeneratorTypes::Type2, PRGType2 > {
    public:
        virtual Data generate() const override final { return Data{ 222 }; }
    };
    template class PRGTmpl < PseudoRandomGeneratorTypes::Type2, PRGType2 >;

    class PRGType3 : public PRGTmpl < PseudoRandomGeneratorTypes::Type3, PRGType3 > {
    public:
        virtual Data generate() const override final { return Data{ 333 }; }
    };
    template class PRGTmpl < PseudoRandomGeneratorTypes::Type3, PRGType3 >;

}

using namespace util;
using namespace std;

int main()
{

    auto rng1 = unique_ptr<PseudoRandomGenerator>(PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type1));
    auto rng2 = unique_ptr<PseudoRandomGenerator>(PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type2));
    auto rng3 = unique_ptr<PseudoRandomGenerator>(PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type3));

    cout << rng1->generate().a << endl;
    cout << rng2->generate().a << endl;
    cout << rng3->generate().a << endl;
}

さらに、一度に 1 つのインスタンスのみが必要で、ヒープを使用したくない場合は、CreatePtr() 関数を使用します。以下に置き換えることができます。

static PseudoRandomGenerator* GetPtr() {
    static PRGImpl _m;
    _m = PRGImpl();
    return &_m;
}

スペシャライゼーションは次のように変更されます。

template <int arbitaryPRGType, typename arbitaryPRGImpl>
const int PRGTmpl<arbitaryPRGType, arbitaryPRGImpl>::TYPE = PseudoRandomGeneratorFactory::get()->Register(arbitaryPRGType, &PRGTmpl<arbitaryPRGType, arbitaryPRGImpl>::GetPtr);

使用パターンは次のように変更されます。

auto rng1 = PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type1);
auto rng2 = PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type2);
auto rng3 = PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type3);

2 番目の例では、マップの代わりに単純な C スタイルの配列を使用して、動的メモリ割り当てを回避できます。

実施例2

C++ テンプレートを使用して、コンパイル時に AbstractFactory にコンストラクター メソッドを動的に登録するから適応

于 2015-11-04T01:40:56.753 に答える
2

Generatorクラスのテンプレートを使用できる場合は、次のようなテンプレート メソッド アプローチを使用できます。

#include<cstdlib>

template<size_t SIZE> class Data { };

template<size_t SIZE>
class Generator {
public:
    using Ret = Data<SIZE>;
    static Data<SIZE> generate() { };
};

template<class Gen>
class PseudoRandomX {
    typename Gen::Ret generate() { return Gen::generate(); }
};

int main() {
    auto prx = new PseudoRandomX<Generator<16>>{};
}

もちろん、簡単にするためにメソッドとして定義しましstaticgenerateが、いくつかの変更を加えることで、ソリューションの非静的バージョンに簡単に切り替えることができます (例として、Generatorクラスがステートレスであるかどうかと、静的メソッドはあなたの要件に一致できませんでした。あなたの質問で見たものからは言えません)。

編集

実行時にジェネレーターが選択されたファクトリーを探しているようです。

少し異なるソリューションに従います。おそらく、要件により適しています。

#include<cstdlib>
#include<memory>

template<size_t SIZE> class Data { };

template<size_t SIZE>
class Generator {
public:
    const static size_t gsize = SIZE;
    using GData = Data<SIZE>;
    static Data<SIZE> generate() { };
};

template<size_t SIZE>
class BasePseudoRandom {
public:
    virtual Data<SIZE> generate() = 0;
};

template <size_t SIZE, class Gen>
class ConcretePseudoRandom: public BasePseudoRandom<Gen::gsize> {
public:
    typename Gen::GData generate() { return Gen::generate(); }
};

class Factory {
public:
    template<class Gen>
    static std::shared_ptr<BasePseudoRandom<Gen::gsize>> create(Gen*) {
        return std::make_shared<ConcretePseudoRandom<Gen::gsize, Gen>>();
    }
};

int main() {
    Generator<16> gen;
std::shared_ptr<BasePseudoRandom<16>> pr = Factory::create(&gen);
    pr->generate();
}

このようにして、ジェネレーターをファクトリ内にプッシュするだけで、そのジェネレーターに基づいて構築された疑似乱数ジェネレーターを取得できます。後者は、明確に定義されたインターフェイスを尊重します。

于 2015-11-03T23:43:21.923 に答える
2

タイプ消去はあなたの友達です。あなたの場合、std::functionボイラープレートの半分を提供する を使用できます。generate残りの半分は、あなたを に変換するラッパーを書くことoperator()です。

template<std::size_t N, class T>
struct generator_adapter
{
    auto operator()() { return generator_.template generate<N>(); }
    T generator_;
};

template<size_t Size>
using AnyRandomGenerator = std::function< Data<Size>() >;

template<size_t Size, class T>
auto as_any_generator(T g)
{
     return AnyRandomGenerator<Size>( generator_adapter<Size,T>{g} );
}

AnyRandomGenerator<4> f = as_any_generator<4>(PseudoRandom1());

関数generateをパブリックにする必要があります。そうしないと、ジェネレーターを のフレンドにすることができますgenerator_adapter

必要に応じて、完全転送を使用するようにこの例を変更します。

于 2015-11-04T02:03:34.033 に答える
1

次のバリエーションは、これまでに提供した情報と一致する少なくともいくつかの目的で機能します。

class Generator {
public:
    template< size_t SIZE >
    Data<SIZE> generate() {
        Data<SIZE> x;
        vgenerate(SIZE, x.pointer_to_internals());
        return x;
    }
protected:
    virtual void vgenerate(size_t size, InternalDataPointer *p) = 0;
};

class PseudoRandomX : public Generator {
    void vgenerate(size_t size, InternalDataPointer *p) override {...}
};

さまざまな目的で機能する別のソリューションは、

template< size_t SIZE >
class Generator {
public:
    virtual Data<SIZE> generate() = 0;
};

template< size_t SIZE >
class PseudoRandomX : public Generator<SIZE> {
    Data<SIZE> generate() override {...}
};
于 2015-11-04T02:21:53.247 に答える