У меня проблема, с которой я пытаюсь создать фабричную функцию,
данный идентификатор и тип вернут правильный (шаблонный) подкласс.
Что это пытается решить:
id()
значения передаются по сети, как только соединение установлено, и указывают получателю, как кодируется последовательность байтов. Приемник заранее знает тип T, который он ожидает, но не знает, как этот тип T кодируется на проводе, пока не получит это значение. Он также определяет, как возвращаемые значения (некоторого типа U, где U может иметь или не быть того же типа, что и T) должны быть упорядочены при возвращении. Этот код используется обычно, то есть есть несколько отправителей / получателей, которые используют / ожидают разные типы; однако типы, используемые между данной парой отправитель / получатель, всегда фиксированы.
Основной набросок проблемы: у нас есть (упрощенный) базовый класс, который определяет id()
template <typename T>
class foo
{
public:
virtual ~foo() { }
// Other methods
// This must return the same value for every type T
virtual std::uint8_t id() const noexcept = 0;
};
Оттуда у нас есть несколько подклассов:
template <typename T>
class bar : public foo<T>
{
public:
std::uint8_t id() const noexcept override { return 1; }
};
template <typename T>
class quux : public foo<T>
{
public:
std::uint8_t id() const noexcept override { return 2; }
};
Для фактической заводской функции мне нужно хранить что-то, что
стирает тип (например, bar, quux), чтобы я мог сохранить фактический
функция создания в однородном контейнере.
По сути, я хочу семантику, которая примерно эквивалентна:
struct creation_holder
{
// Obviously this cannot work, as we cannot have virtual template functions
template <typename T>
virtual foo<T>* build() const;
};
template <template <typename> class F>
struct create : public creation_holder
{
// As above
template <typename T>
foo<T>* build() const override
{
return new F<T>();
}
};
std::unordered_map<std::uint8_t, create*>& mapping()
{
static std::unordered_map<std::uint8_t, create*> m;
return m;
}
template <typename T, template <typename> class F>
bool register_foo(F<T> foo_subclass,
typename std::enable_if<std::is_base_of<foo<T>, F<T>>::value>::type* = 0)
{
auto& m = mapping();
const auto id = foo_subclass.id();
creation_holder* hold = new create<F>();
// insert into map if it's not already present
}
template <typename T>
foo<T>* from_id(std::uint8_t id)
{
const auto& m = mapping();
auto it = m.find(id);
if(it == m.end()) { return nullptr; }
auto c = it->second;
return c->build<T>();
}
Я поиграл с несколькими идеями, чтобы попытаться получить что-то подобное
семантика, но без удачи. Есть ли способ сделать это (мне все равно, если
реализация существенно отличается).
Некоторые типы утилит для передачи типов и групп типов:
template<class...Ts>
struct types_t {};
template<class...Ts>
constexpr types_t<Ts...> types{}; // C++14. In C++11, replace types<T> with types_t<T>{}. Then again, I don't use it.
template<class T>
struct tag_t {};
template<class T>
constexpr tag_t<T> tag{}; // C++14. In C++11, replace tag<T> with tag_t<T>{} below
Теперь мы пишем полифабрику.
Вот ifactory
:
template<template<class...>class Z, class T>
struct ifactory {
virtual std::unique_ptr<Z<T>> tagged_build(tag_t<T>) const = 0;
virtual ~ifactory() {}
};
Вы передаете тег, который хотите построить, и получаете объект. Довольно просто
Затем мы связываем их (это было бы проще в C ++ 171, но ты просил C ++ 11):
template<template<class...>class Z, class Types>
struct poly_ifactory_impl;
Корпус одного типа:
template<template<class...>class Z, class T>
struct poly_ifactory_impl<Z,types_t<T>>:
ifactory<Z, T>
{
using ifactory<Z, T>::tagged_build;
};
случай 2+:
template<template<class...>class Z, class T0, class T1, class...Ts>
struct poly_ifactory_impl<Z,types_t<T0, T1, Ts...>>:
ifactory<Z, T0>,
poly_ifactory_impl<Z, types_t<T1, Ts...>>
{
using ifactory<Z, T0>::tagged_build;
using poly_ifactory_impl<Z, types_t<T1, Ts...>>::tagged_build;
};
Мы импортируем tagged_build
метод вниз в производные классы. Это означает, что наиболее производный poly_ifactory_impl
имеет все tagged_build
методы в том же наборе перегрузки. Мы будем использовать это для отправки им.
Тогда мы завернем это довольно:
template<template<class...>class Z, class Types>
struct poly_ifactory:
poly_ifactory_impl<Z, Types>
{
template<class T>
std::unique_ptr<Z<T>> build() const {
return this->tagged_build(tag<T>);
}
};
обратите внимание, что я возвращаю unique_ptr
; вытащить сырой T*
от фабричного метода есть кодовый запах.
Кто-то с poly_ifactory<?>
просто делает ->build<T>()
и игнорирует tagged_
перегрузки (если они не хотят их; я оставляю их открытыми). каждый tagged_build
виртуально, но build<T>
не является. Вот так мы эмулируем функцию виртуального шаблона.
Это обрабатывает интерфейс. На другом конце мы не хотим реализовывать каждый build(tag_t<T>)
вручную. Мы можем решить это с CRTP.
template<class D, class Base, template<class...>class Z, class T>
struct factory_impl : Base {
virtual std::unique_ptr<Z<T>> tagged_build( tag_t<T> ) const override final {
return static_cast<D const*>(this)->build_impl( tag<T> );
}
using Base::build;
};
template<class D, class Base, template<class...>class Z, class Types>
struct poly_factory_impl;
1 тип корпуса:
template<class D, class Base, template<class...>class Z, class T0>
struct poly_factory_impl<D, Base, Z, types_t<T0>> :
factory_impl<D, Base, Z, T0>
{
using factory_impl<D, Base, Z, T0>::tagged_build;
};
тип 2+:
template<class D, class Base, template<class...>class Z, class T0, class T1, class...Ts>
struct poly_factory_impl<D, Base, Z, types_t<T0, T1, Ts...>> :
factory_impl<D, poly_factory_impl<D, Base, Z, types_t<T1, Ts...>>, Z, T0>
{
using factory_impl<D, poly_factory_impl<D, Base, Z, types_t<T1, Ts...>>, Z, T0>::tagged_build;
};
что это делает, это написать серию tagged_build(tag_t<T>)
перегрузки ifactory
методы и перенаправляет их на D::build_impl(tag_t<T>)
, где D
является теоретическим производным типом.
Причудливая «обходная база» существует во избежание необходимости использовать виртуальное наследование. Мы наследуем линейно, каждый шаг реализуя один tagged_build(tag<T>)
перегрузки. Все они отправляют вниз не виртуально используя CRTP.
Использование выглядит так:
struct bar {};
using my_types = types_t<int, double, bar>;
template<class T>
using vec = std::vector<T>;
using my_ifactory = poly_ifactory< vec, my_types >;
struct my_factory :
poly_factory_impl< my_factory, my_ifactory, vec, my_types >
{
template<class T>
std::unique_ptr< vec<T> > build_impl( tag_t<T> ) const {
return std::make_unique< std::vector<T> >( sizeof(T) );
// above is C++14; in C++11, use:
// return std::unique_ptr<vec<T>>( new vec<T>(sizeof(T)) );
}
};
и экземпляр my_factory
удовлетворяет my_ifactory
интерфейс.
В этом случае мы создаем уникальный ptr для вектора T
с количеством элементов, равным sizeof(T)
, Это просто игрушка.
Дизайн псевдокода.
Интерфейс имеет
template<class T> R build
функция. Он отправляет в
virtual R tagged_build(tag_t<T>) = 0;
методы.
T
S, о которых идет речь, извлекаются из types_t<Ts...>
список. Поддерживаются только эти типы.
На стороне реализации мы создаем линейное наследование помощников CRTP. Каждый наследует от последнего и переопределяет virtual R tagged_build(tag_t<T>)
,
Реализация tagged_build
использует CRTP для приведения this
указатель на более производный класс и вызов build_impl(tag<T>)
в теме. Это пример не-динамического полиморфизма.
Так что звонки идут build<T>
в virtual tagged_build(tag_t<T>)
в build_impl(tag<T>)
, Пользователи просто взаимодействуют с одним шаблоном; Разработчики просто реализуют один шаблон. Клей посередине — virtual tagged_build
— генерируется из types_t
список типов.
Это около 100 строк «связующего» или вспомогательного кода, и взамен мы получаем эффективные методы виртуальных шаблонов.
1 в C ++ 17 это становится:
template<template<class...>class Z, class...Ts>
struct poly_ifactory_impl<Z,types_t<Ts...>>:
ifactory<Z, Ts>...
{
using ifactory<Z, Ts>::tagged_build...;
};
что намного проще и понятнее.
Наконец, вы можете сделать что-то неясно как это без центрального списка типов. Если вы знаете, как вызывающий, так и вызываемый знают тип, который вы могли бы передать typeid
или же typeindex
в ifactory
пройди void*
или что-то похожее на механизм виртуальной диспетчеризации и приведение / проверка на нулевое значение / поиск в карте типов.
Внутренняя реализация будет похожа на эту, но вам не придется публиковать types_t
как часть вашего формального (или двоичного) интерфейса.
Внешне вы должны просто знать, какие типы поддерживаются. Во время выполнения вы можете получить нулевой умный (или тупой, ick) указатель, если вы передадите неподдерживаемый тип.
С небольшой осторожностью вы могли бы сделать и то и другое. Предоставьте эффективный и безопасный механизм для получения известных типов во время компиляции, примененных к шаблону. Также предоставьте интерфейс на основе «try», который использует эффективную известную во время компиляции систему (если тип соответствует) и использует неэффективную проверенную среду выполнения. Это можно сделать по эзотерическим причинам обратной двоичной совместимости (поэтому новое программное обеспечение может подключаться через устаревший интерфейс к новым или старым реализациям API и динамически обрабатывать наличие старой реализации API).
Но в этот момент вы рассматривали возможность использования COM?
Других решений пока нет …