替代虚拟模板

Alternative to virtual templates

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

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 {...}
};

不幸的是,我无法将数据 class 的 SIZE 参数更改为非模板运行时参数。此外,我需要实际的生成器实例作为运行时决策,因为用户可以选择生成器算法。 如果可能的话,我更喜欢类型安全的解决方案(即没有 boost::any)。

我知道虚拟模板是不可能的。还有其他方法可以解决这个问题吗?

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

#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 classes 是否是无状态的,静态方法不能满足你的要求,我不能从你的问题中看到这一点。

编辑

我刚刚看到您正在寻找一个在运行时选择生成器的工厂。

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

#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();
}

这样,您只需将生成器推入工厂,然后取回基于该生成器构建的伪随机生成器,后者遵循定义良好的接口。

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

设计分为两部分(命名空间实用程序)。第一部分由 PseudoRandomGenerator Base 和 Template class 以及所有生成器的 Factory 组成。第二部分用于实现各种数据生成算法。第二部分中的每个实现 class 都可以拆分成单独的文件(在我们的例子中是 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

类型擦除是你的朋友。在您的情况下,您可以使用 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 函数需要 public,或者你可以让你的生成器成为 generator_adapter 的朋友。

如有必要,修改此示例以使用完美转发。

以下变体至少可以用于与您目前提供的信息一致的某些目的。

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 {...}
};