我正在开发一个工厂,可以注册和创建具有不同参数类型和数字的类。我通过互联网搜索,我设法创建这个类:
template< typename Key, typename BaseClass >
class Factory {
static_assert( std::has_virtual_destructor< BaseClass >::value,
"BaseClass must have a virtual destructor" );
public:
template< typename DerivedClass, typename ... Args >
void register_creator( const Key& key )
{
static_assert( std::is_base_of< BaseClass, DerivedClass >::value,
"DerivedClass must be a subclass of BaseClass" );
static_assert( std::is_constructible< DerivedClass, Args... >::value,
"DerivedClass must be constructible with Args..." );
creators_.emplace(
CreatorKey { key, create_function_type_index< Args... >() },
reinterpret_cast< CreateFunc< > >( create_function_impl<
DerivedClass, Args... > ) );
}
template< typename ... Args >
std::unique_ptr< BaseClass > create(
const Key& key,
Args&&... args ) const
{
auto creator = creators_.find(
{ key, create_function_type_index< Args... >() } );
if( creator != creators_.end() ) {
return reinterpret_cast< CreateFunc< Args... > >( creator->second )(
std::forward< Args>( args )... );
} else {
return {};
}
}
private:
template< typename ... Args >
static std::type_index create_function_type_index()
{
return {typeid( CreateFunc<Args...> )};
}
template< typename DerivedClass, typename ... Args >
static std::unique_ptr< BaseClass > create_function_impl(
const Args&... args )
{
return std::unique_ptr< BaseClass > { new DerivedClass {
std::forward< const Args& >( args )... } };
}
template< typename ... Args >
using CreateFunc = typename std::add_pointer< std::unique_ptr< BaseClass >( const Args&... ) >::type;
using CreatorKey = std::pair< Key, std::type_index >;
std::map< CreatorKey, CreateFunc< > > creators_;
};
我的目标是能够运行这种代码:
class ___A___ {};
class ___B___ {};
class ___Base___ {
public:
virtual ~___Base___() = default;
protected:
___Base___( ___A___ a, ___B___ b ) : a_( a ), b_( b ) {
}
protected:
___A___ a_;
___B___ b_;
};
class ___Derived___: public ___Base___ {
public:
___Derived___( ___A___& a, ___B___& b ) : ___Base___( a, b ) {
}
};
class ___Derived2___: public ___Base___ {
public:
___Derived2___( ___A___ a, ___B___ b ) : ___Base___( a, b ) {
}
};
class ___Derived3___: public ___Base___ {
public:
___Derived3___( ___A___& a, ___B___ b ) : ___Base___( a, b ) {
}
};
Factory< std::string, ___Base___ > factory;
factory.register_creator< ___Derived___, ___A___ &, ___B___& >( "Derived" );
factory.register_creator< ___Derived2___, ___A___, ___B___ >( "Derived2" );
factory.register_creator< ___Derived3___, ___A___ &, ___B___ >( "Derived3" );
___A___ a;
___B___ b;
auto D = factory.create( "Derived", a, b );
auto D2 = factory.create( "Derived2", a, b );
auto D3 = factory.create( "Derived3", a, b );
这些寄存器对于参考和值参数都非常有效,但我无法使用创建者来设置它们。在我的调试过程中,我看到所有参数都是通过引用给出的,而不是通过值。
答案 0 :(得分:0)
使用这样的推断类型:
auto D = factory.create( "Derived", a, b );
为了进行演员表演你应该永远不会。此处推断的类型基本上是&decltype(a)
(不是decltype(&a)
;对a
类型的引用,而不是a
指针的类型。
return reinterpret_cast< CreateFunc< Args... > >( creator->second )(
std::forward< Args>( args )... );
这需要精确类型匹配。当这与类型匹配时,意外地发生 。
要做你想做的事,即使你只想支持“完全匹配”的类型,你也要完成一大堆工作。
给定类型T
,可以通过多种方式将其用作函数的参数。
可以移动进入参数:T&&
。一个“下沉”的论点。
它可以重复到参数中:T
。一个“价值”的论点。
可以通过参数引用,但保证不会被修改T const&
。一个“在”的论点。
可以通过参数引用,并可能修改T&
。 “进/出”的论点。
其他选项是可能的,但不太有趣。
在您注册函数时,您必须计算T
类型以及您正在处理的上述4个案例中的哪一个。
然后,在您想要调用您的函数时,您必须计算出T
类型以及您支持的上述4个类型。
然后你必须注入代码以将传入的参数粘合到要调用的函数。
我们可以通过比C ++代码中相对常用的4种参数更少的参数来简化这一点。例如,“value”通常可以以一个额外的std::move
构造为代价来代替“sink”。
如果您不希望调用者必须知道完全被调用类型需要什么类型的协议,则必须键入删除详细信息。
想象一下适配器:
template<class T>
struct arg_adapter;
它可以由T&&
,T const&
或T&
构建。构造它时,它会记录一个指向传入对象的void*
指针,以及它传递的3种类型中的哪一种。
它有.get<U>
方法,U
可以是T&&
,T const&
或T&
中的任何一种。它会检查.get
是否兼容;如果是,则会将存储在void*
内的remove_reference_t<U>*
转换为static_cast<U>
,对其进行解引,然后exit(-1)
。
如果失败,则抛出异常或调用std::function< Out(arg_adapter<std::decay_t<Args>>...) >
或其他某些内容。
现在存储std::function< Out(Args...) >
而不是template<class Dest>
using p_getter = Dest(*)(void*);
template<class T>
struct arg_vtable {
p_getter<T&> ref = 0;
p_getter<T const&> cref = 0;
p_getter<T> value = 0;
p_getter<T&&> move = 0;
};
template<class Dest, class Src>
p_getter<Dest> make_getter() {
return [](void* ptr)->Dest{
return (Src&&)(*static_cast<std::decay_t<Src>*>(ptr));
};
}
template<class T>
arg_vtable<T> make_arg_vtable( tag_t<T const&> ) {
return {
0,
make_getter<T const&, T const&>(),
make_getter<T, T const&>(),
0
};
}
template<class T>
arg_vtable<T> make_arg_vtable( tag_t<T&> ) {
return {
make_getter<T&, T&>(),
make_getter<T const&, T&>(),
make_getter<T, T&>(),
0
};
}
template<class T>
arg_vtable<T> make_arg_vtable( tag_t<T&&> ) {
return {
0,
make_getter<T const&, T&&>(),
make_getter<T, T&&>(),
make_getter<T&&, T&&>(),
};
}
template<class T>
arg_vtable<T> make_arg_vtable( tag_t<T const&&> ) {
return make_arg_vtable( tag_t<T const&>{} );
}
template<class T, class U>
arg_vtable<T> make_arg_vtable( tag_t<T volatile&&> ) {
return make_arg_vtable( tag_t<T&&>{} );
}
template<class T, class U>
arg_vtable<T> make_arg_vtable( tag_t<T volatile&> ) {
return make_arg_vtable( tag_t<T&>{} );
}
template<class T, class U>
arg_vtable<T> const* get_arg_vtable( tag_t<U> tag ) {
static const arg_vtable<T> retval = make_arg_vtable<T>(tag);
return &retval;
}
template<class T>
struct arg_adapter {
arg_vtable<T> const* vtable = 0;
void* pvoid = 0;
template<class U>
arg_adapter( U&& u ):
vtable( get_arg_vtable<T>( tag_t<U&&>{} ) ),
pvoid( (void*)std::addressof(u) )
{}
T get(tag_t<T>) {
if (!vtable->value) throw std::invalid_argument("value");
return vtable->value(pvoid);
}
T&& get(tag_t<T&&>) {
if (!vtable->move) throw std::invalid_argument("move");
return vtable->move(pvoid);
}
T& get(tag_t<T&>) {
if (!vtable->ref) throw std::invalid_argument("ref");
return vtable->ref(pvoid);
}
T const& get(tag_t<T const&>) {
if (!vtable->ref) throw std::invalid_argument("cref");
return vtable->cref(pvoid);
}
};
template<class R, class...Args>
using adapt_function = std::function< R(arg_adapter<std::decay_t<Args>>...) >;
template<class R, class...Args, class F>
adapt_function<R, Args...> adapt_args( F&& f ) {
return [f=std::forward<F>(f)](arg_adapter<std::decay_t<Args>>... args)->R{
return f( args.get( tag_t<Args>{} )... );
};
}
using func_token = std::shared_ptr<void>;
template<class R, class...Args>
R invoke_token( func_token f, Args&&... args ) {
auto const* pf = static_cast<adapt_function<R, Args...>*>(f.get());
return (*pf)( std::forward<Args>(args)... );
}
template<class R>
struct factories {
std::map< std::string, func_token > funcs;
template<class...Args, class F>
void add( std::string s, F&& f ) {
funcs[s] = std::make_shared<adapt_function<R,Args...>>(adapt_args<R,Args...>(std::forward<F>(f)));
}
template<class...Args>
R invoke( std::string s, Args&&... args ) {
auto it = funcs.find(s);
if (it==funcs.end()) throw std::invalid_argument("s");
return invoke_token<R>( it->second, std::forward<Args>(args)... );
}
};
。
你知道,像这样:
模板 struct tag_t {};
arg_adapter::get
它需要转发重载到make_arg_vtable
,就像我怀疑{{1}}那样。
总的来说,我认为这是一个坏主意,因为调用者只处理cv转换而没有别的。任何其他原因都会导致崩溃或其他未定义的行为。