避免使用虚拟模板函数
Avoid virtual template functions
任何人都可以在以下代码中提出一种避免虚拟模板函数的技术吗? 我已经阅读了其他几篇文章,但我看不出如何将这些解决方案应用于这种情况。
我正在构建一个包含模板化类层次结构的库。 我想创建一个"工厂"函数数组,可用于按名称实例化派生类(例如,基于命令行参数)。
如果可能的话,我希望每个派生类能够在它自己的 .hpp 或 .cpp 文件中注册自己(而不是必须在某处维护所有可能的派生类的单个列表)。
下面的代码几乎可以工作,除了尝试使用虚拟模板函数的致命缺陷。
//
// This code would appear in a library
//
template<class T>
class Base {
public:
Base(const char* /*param*/) {}
};
//
// Description of each derived class.
// We need this non-templated base class so we can store all our
// descriptions in a single vector
//
class DescriptionBase {
private:
const char *description;
const char *name;
public:
DescriptionBase(const char* pDesc, const char* pName) : description(pDesc), name(pName){
// Whenever a Description object is created, it is automatically registered with the
// global descriptionList. This allows us to register derived classes in their own
// .cpp/.hpp file (as opposed to keeping a centralized list of all derived classes).
descriptionList.push_back(this);
}
// FAIL Can't have virtual template functions
virtual template<class T>
Base<T> *make(const char *param) {return new Base<T>(param); }
static vector<DescriptionBase *> descriptionList;
};
//global list of all derived classes
vector<DescriptionBase *> DescriptionBase::descriptionList;
// We use the template to store the type of the derived class
// for use in the make method
template<template<typename> class D>
class Description : public DescriptionBase {
public:
Description(const char* pDesc, const char* pName) : DescriptionBase(pDesc, pName) {}
template<class T>
Base<T> *make(const char *params) {
return new D<T>(params);
}
};
//
// These derived classes may be part of the library, or they may be
// written by users of the library.
//
template<class T>
class DerivedA : public Base<T> {
public:
DerivedA(const char* param) : Base<T>(param) {return;}
};
Description<DerivedA> derivedA("derivedA", "This is the first derived class");
template<class T>
class DerivedB : public Base<T> {
DerivedB(const char* param) : Base<T>(param) {return;}
};
Description<DerivedA> derivedB("derivedA", "This is the second derived class");
//
// Example code written by the user of the library.
//
//
int main(int argc, char *argv[]) {
// Using a descriptionList is just a short-cut here.
// Final code will use a map.
int indexOfDerivedA = 0;
Base<int> *intItem = DescriptionBase::descriptionList[indexOfDerivedA]->make<int>("parameter to derived type's constructor");
Base<char> *charItem = DescriptionBase::descriptionList[indexOfDerivedA]->make<char>("parameter to derived type's constructor");
}
这是一个编译时标记和类型:
template<class T>
struct tag_t { constexpr tag_t() {}; };
template<class T> constexpr tag_t tag<T>{};
这是一个类型列表:
template<class...>struct types_t{constexpr types_t(){}; using type=types_t;};
template<class...Ts>constexpr types_t<Ts...> types{};
这将映射类型列表的内容:
template<template<class...>class Z, class types>
struct fmap{};
template<template<class...>class Z, class types>
using fmap_t=typename fmap<Z,types>::type;
template<template<class...>class Z, template<class...>class types, class...Ts>
struct fmap<Z,types<Ts...>> {
using type=types<Z<Ts...>>;
};
现在让我们列出工厂:
template<class...Args>
struct build_tagged_sig {
template<class T>
using result = std::unique_ptr<T>(tag_t<T>,Args...);
};
template<template<class...>class Out, class types, class...Args>
using tagged_factories =
fmap_t<
std::function,
fmap_t<
build_tagged_sig<Args...>::template result,
fmap_t< Out, types >
>
>;
这会将types
应用于模板:
template<template<class...>class Z, class types>
struct apply_types {};
template<template<class...>class Z, class types>
using apply_types_t = typename apply_types<Z,types>::type;
template<template<class...>class Z, template<class...>class types, class...Ts>
struct apply_types<Z, types<Ts...>> {
using type=Z<Ts...>;
};
template<template<class...>class Z>
struct applier {
template<class types>
using result=apply_types_t<Z,types>;
};
这篇 SO 帖子展示了如何重载多个 lambda 或std::function
s。
using my_types = types_t< std::int8_t, std::int16_t, std::int32_t, std::int64_t >;
using magic_factory =
apply_types_t<
overload,
tagged_factories< Base, my_types, const char* > >
>;
这是一个超载
std::function< std::unique_ptr<Base<std::int8_t>>( tag_t<Base<std::int8_t>>, const char* ) >,
std::function< std::unique_ptr<Base<std::int16_t>>( tag_t<Base<std::int16_t>>, const char* ) >,
std::function< std::unique_ptr<Base<std::int32_t>>( tag_t<Base<std::int32_t>>, const char* ) >,
std::function< std::unique_ptr<Base<std::int64_t>>( tag_t<Base<std::int64_t>>, const char* ) >
我们现在写一个寄存器工厂:
template<class F, class...Ts>
magic_factory make_poly_factory( types_t<Ts...>, F&& f ) {
return magic_factory(
(void(tag<Ts>), f)...
);
}
template<class F>
magic_factory make_poly_factory( F&& f ) {
return make_poly_factory( my_types{}, f );
}
它创建 N 个f
副本,并将每个副本存储在一个std::function
中,全部存储在一个对象中。
获取返回值,您可以通过重载解析调用单个值。
template<class T>
std::unique_ptr<Base<T>> factory_A_impl( tag_t<Base<T>>, const char* param) {
return new DerivedA<T>(param);
}
auto magic = magic_factory( my_types{}, [](auto tag, const char* param){
return factory_A_impl( tag, param );
});
std::unique_ptr<Base<std::int8_t>> bob = magic( tag<std::int8_t>, "hello" );
bob
是运行时实际上是DerivedA<std::int8_t>
的Base<std::int8_t>
的unique_ptr
。
这可能有 tpyos。
这篇文章的大部分内容是元编程,用于设置单个对象,该对象通过tag_t<T1>
重载每个对象tag_t<T0>
而不重复我自己。 您可以为您的 4 种类型手动执行此操作。
我使用的重载假定我们不是在使用模板参数和类型擦除每个重载的单个 lambda,而是一组 lambda。 第一个会让上面的一些事情变得更容易。
最终用户学生只需要创建一个函数对象,该对象采用tag_t<X>
和const char*
并返回unique_ptr<X>
并且复制成本低廉,然后从中创建magic_factory
(将其类型擦除为一堆std::function
)。
描述库变为:
struct Description {
char const* description = 0;
char const* name = 0;
magic_factory factory;
template<class T>
std::unique_ptr<Base<T>>
make(const char *param) {
return factory( tag<T>, param );
}
};
多态性现在在magic_factory
范围内。 存储Description
的实例,而不是指向它们的指针,因为它们是值类型的多态性。
看,很容易。
添加更多模板参数只会给fmap
的东西增加一些复杂性,并为magic_factory
的创建者增加更多的责任。
您需要跨产品操作从包含 4 个元素的一个列表中生成 64 种不同的类型集。 这将是types_t
的types_t
.
称之为my_many_types
。
然后
using magic_factory =
apply_types_t<
overload,
tagged_factories< applier<Base>::template result, my_types, const char* > >
>;
完成后,我们现在有 64 个重载,其签名如下:
std::unique_ptr<Base<std::int8_t, std::int16_t, std::int8_t>>(
tag_t<Base<std::int8_t, std::int16_t, std::int8_t>>,
const char*
)
现在我们可以手动完成所有这些操作。
构建如下表:
template<class T>
using factory_ptr = std::unique_ptr<T>( void*, tag_t<T>, const char* );
using factory_table = std::tuple<
factory_ptr< Base< std::int8_t, std::int8_t, std::int8_t > >,
factory_ptr< Base< std::int8_t, std::int8_t, std::int16_t > >,
factory_ptr< Base< std::int8_t, std::int8_t, std::int32_t > >,
factory_ptr< Base< std::int8_t, std::int8_t, std::int64_t > >,
factory_ptr< Base< std::int8_t, std::int16_t, std::int8_t > >,
factory_ptr< Base< std::int8_t, std::int16_t, std::int16_t > >,
factory_ptr< Base< std::int8_t, std::int16_t, std::int32_t > >,
factory_ptr< Base< std::int8_t, std::int16_t, std::int64_t > >,
。
factory_ptr< Base< std::int64_t, std::int64_t, std::int64_t > >
>;
现在是一个神奇的工厂:
struct magic_factory {
std::unique_ptr<void, void(*)(void*)> state;
factory_table table;
template<class T0, class T1, class T2>
std::unique_ptr<Base<T0, T1, T2>> make( char const* param ) {
auto f = std::get<factory_ptr< Base< T0, T1, T2 > >>( table );
return f( state.get(), param );
}
magic_factory(magic_factory&&)=default;
template<class T,
class=std::enable_if_t<!std::is_same<std::decay_t<T>, magic_factory>::value>
>
magic_factory( T&& t ) {
ptr = {new std::decay_t<T> >(std::forward<T>(t)),
[](void* ptr){
delete static_cast< std::decay_t<T>* >(ptr);
}
};
// 64 lines like this:
std::get<factory_ptr< Base< std::int8_t, std::int8_t, std::int8_t > >>( table )
= +[](void* pvoid, tag_t<Base< std::int8_t, std::int8_t, std::int8_t >> tag, char const* param)->std::unique_ptr<Base< std::int8_t, std::int8_t, std::int8_t >>
{
auto*pt = static_cast<std::decay_t<T>*>(pvoid);
return (pt)(tag, param);
};
}
};
我们构建了一个类型擦除函数的tuple
和一个指向其参数的 void 指针,并调度我们自己。
您也可以使用上述一些机器来自动化此操作。 手动表确实提供了效率,因为我们不会像std::function
版本那样复制可调用对象的状态 N 次。
另一种方法是使用我的类型擦除类型擦除解决方案。
我们编写一组模板any_method
,执行标记技巧来创建Base<A,B,C>
对象。
然后,我们在每个any_method
上创建一个super_any
。
然后我们从中继承并包装您的make
以发送给那些any_method
。
这可能与上面的手动方法一样有效。
template<class T0, class T1, class T2>
auto make_base = make_any_method<std::unique_ptr<Base<T0, T1, T2>>(char const* name)>(
[](auto* p, char const* name)
{
return p->make<T0, T1, T2>( name );
}
);
template<class T0, class T1, class T2>
using base_maker = decltype(make_base);
现在我们的工厂是一家:
super_any< base_maker<std::int8_t, std::int8_t, std::int8_t > > bob;
bob
可以存储指向实现make<T0, T1, T2>
与int8_t, int8_t, int8_t
匹配的类的指针。
再添加 64 行并完成。
bob
存储的类型根本不需要具有公共基类。 它不使用C++继承来实现多态性,而是使用手动类型擦除。
称它为你只是
auto r = (bob->*make_base<std::int8_t, std::int8_t, std::int8_t>)("hello");
自然地将更多类型传递给super_any
,它支持更多类型的make_base
。
- C++无法定义虚拟函数 OUTER 类和头文件
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 尝试将unique_ptrs推送到向量时使用纯虚拟函数错误
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- 当覆盖存在时调用基本虚拟"binded to object"函数
- 如何在C++中伪造虚拟可变参数函数模板?
- 类型擦除的std::function与虚拟函数调用的开销
- 重写虚拟函数和继承
- 是否可以使用函数指针调用虚拟析构函数?
- 在没有动态内存的世界中,我是否需要虚拟析构函数?
- 虚拟继承基构造函数消除
- "虚拟""覆盖"析构函数
- 类中的虚拟布尔函数参数不起作用
- 用纯虚拟函数兜圈子
- 将C++子类成员函数(虚拟实现)传递给 C 类型函数指针
- 尝试在 QLabel 上绘画失败(无法在没有对象的情况下调用成员函数"虚拟无效 QLabel::p aintEvent(QPaintEvent*)")
- 声明析构函数虚拟就足够了吗?
- 视觉 C++当我们在基类中使函数成为纯虚拟时,那么在子类中再次使相同的函数虚拟的必要性是什么
- 重载函数(虚拟/非虚拟)