Я читаю книгу по метапрограммированию и на батутах есть отделение:
struct generic_t
{
void* obj;
void(*del)(void*);
};
template <typename T> // outer template parameter
generic_t copy_to_generic(const T& value)
{
struct local_cast // local class
{
static void destroy(void* p) // void*-based interface
{
delete static_cast<T*>(p); // static type knowledge
}
};
generic_t p;
p.obj = new T(value); // information loss: copy T* to void*
p.del = &local_cast::destroy;
return p;
}
Я полностью понимаю, как это работает, но я не знаю, в чем его применение! и где вы обычно используете эту технику? Доза кто-нибудь знает об этом? Спасибо 🙂
Я использую это во многих местах в моих программах. В этом методе мне нравится то, что вы можете хранить список несвязанных типов. Например, я видел много кода, который выглядел так:
struct Abstract { virtual ~Abstract() = default; };
template<typename P>
struct AbstractHandler : Abstract {
virtual void handle(P) = 0;
};
template<typename P, typename H>
struct Handler : AbstractHandler<P>, private H {
void handle(P p) override {
H::handle(p);
}
};
struct Test1 {};
struct Test1Handler {
void handle(Test1) {}
};
struct Test2 {};
struct Test2Handler {
void handle(Test2) {}
};
int main() {
std::vector<std::unique_ptr<Abstract>> handlers;
handlers.emplace_back(std::make_unique<Handler<Test1, Test1Handler>>());
handlers.emplace_back(std::make_unique<Handler<Test2, Test2Handler>>());
// some code later....
dynamic_cast<AbstractHandler<Test1>*>(handlers[0].get())->handle(Test1{});
dynamic_cast<AbstractHandler<Test2>*>(handlers[1].get())->handle(Test2{});
}
Динамическое приведение добавляет ненужные накладные расходы на программу. Вместо этого вы можете использовать тип easure точно так же, как тот, который вы сделали, чтобы избежать этих накладных расходов.
Плюс, нет никаких причин для Abstract
даже существовать. Это интерфейс, который не предоставляет никакой полезной функции. Настоящая необходимость здесь — это хранить список не связанных между собой интерфейсов.
Допустим, мы просто набираем easure, чтобы разрешить copy_to_generic
бросить экземпляр в родительский класс.
template <typename Parent, typename T>
generic_t to_generic(T&& value) // forward is better.
{
struct local_cast
{
static void destroy(void* p)
{
// we cast to the parent first, and then to the real type.
delete static_cast<std::decay_t<T>*>(static_cast<Parent*>(p));
}
};
generic_t p;
p.obj = static_cast<Parent*>(new std::decay_t<T>(std::forward<T>(value)));
p.del = &local_cast::destroy;
return p;
}
Посмотрите на этот код с типом easure:
// No useless interface
template<typename P>
struct AbstractHandler {
// No virtual destructor needed, generic_t already has a virtual destructor via `del`
virtual void handle(P) = 0;
};
template<typename P, typename H>
struct Handler : private H {
void handle(P p) override {
H::handle(p);
}
};
struct Test1 {};
struct Test1Handler {
void handle(Test1) {}
};
struct Test2 {};
struct Test2Handler {
void handle(Test2) {}
};
int main() {
std::vector<generic_t> handlers;
handlers.emplace_back(
to_generic<AbstractHandler<Test1>>(Handler<Test1, Test1Handler>{})
);handlers.emplace_back(
to_generic<AbstractHandler<Test2>>(Handler<Test2, Test2Handler>{})
);
// some code later....
static_cast<AbstractHandler<Test1>*>(handlers[0].obj)->handle(Test1{});
static_cast<AbstractHandler<Test2>*>(handlers[1].obj)->handle(Test2{});
}
Нет пустого интерфейса и динамических преобразований! Этот код делает то же самое, что и другой, но быстрее.
Других решений пока нет …