Альтернатива виртуальным шаблонам

У меня есть класс, хранящий фиксированный размер данных

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).

Я знаю, что виртуальные шаблоны не возможны. Есть ли другой способ решить эту проблему?

2

Решение

Следующий вариант будет работать по крайней мере для некоторых целей в соответствии с информацией, которую вы предоставили до сих пор.

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

Другие решения

Тип erasure это твой друг. В вашем случае вы можете использовать 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,

Измените этот пример, чтобы при необходимости использовать совершенную пересылку.

2

Вот пример использования CRTP, который динамически регистрирует набор генераторов во время выполнения (c-startup). Шаблон одноэлементной фабрики используется для динамического создания новых экземпляров данных во время выполнения в соответствии с выбранными алгоритмами, определенными в реализации.

Дизайн разделен на две части (утилита namespace). Первая часть состоит из PseudoRandomGenerator Base и класса Template, а также Factory для всех генераторов. Вторая часть предназначена для реализации различных алгоритмов генерации данных. Каждый из классов реализации во второй части можно разбить на отдельные файлы (в нашем случае 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;
}

Кроме того, если вам нужен только один экземпляр за раз и вы хотите не использовать кучу, функция 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 вместо карты, чтобы избежать динамического выделения памяти.

Рабочий пример 2

Адаптированы из
Динамически регистрировать методы конструктора в AbstractFactory во время компиляции с использованием шаблонов C ++

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 классы не имеют состояния или нет, и статический метод не может соответствовать вашим требованиям, я не могу сказать это из того, что вижу в вашем вопросе)

РЕДАКТИРОВАТЬ

Я только что видел, что вы ищете завод с генератором, выбранным во время выполнения.

Это следует за немного другим решением, возможно, оно лучше соответствует вашим требованиям:

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

Таким образом, вы просто помещаете свой генератор в фабрику и возвращаете генератор псевдослучайного числа, построенный на этом генераторе, последний уважает четко определенный интерфейс.

2
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector