替代虚拟模板

时间:2015-11-03 23:23:13

标签: c++ templates c++14 virtual-functions dynamic-dispatch

我有一个存储固定大小数据量的类

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 个答案:

答案 0 :(得分:2)

如果您可以将 Generator 类转换为模板,则可以使用以下模板方法:

XY[i+1,2]

当然,为了简单起见,我将#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>>{}; } 方法定义为static,但您可以通过一些更改轻松切换到解决方案的非静态版本(例如,它可能取决于事实上你的generate类是无状态的,静态方法不能满足你的要求,我不能从你在问题中看到的那个说出来。

修改

我刚刚看到你正在寻找一个在运行时选择发电机的工厂。

它遵循略有不同的解决方案,也许它更符合您的要求:

Generator

这样,你只需在工厂内推送你的发电机,然后取回一个基于该发电机构建的伪随机发生器,后者尊重一个定义良好的接口。

答案 1 :(得分:2)

这是一个使用CRTP的示例,它在运行时动态注册一组生成器(c-startup)。单例工厂模式用于根据实现中定义的选定算法在运行时动态创建新的数据实例。

设计分为两部分(namespace util)。第一部分包括PseudoRandomGenerator Base和Template类以及所有生成器的Factory。第二部分是您实现各种数据生成算法。第二部分中的每个实现类可以拆分为单独的文件(在我们的例子中为3)。

Working example 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;
}

此外,如果您一次只需要一个实例并且希望不使用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);

在第二个例子中,我们可以使用普通的C风格数组而不是地图来避免动态内存分配。

Working example 2

改编自 Dynamically register constructor methods in an AbstractFactory at compile time using C++ templates

答案 2 :(得分: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的朋友。

修改此示例以在必要时使用完美转发。

答案 3 :(得分: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 {...}
};