C ++ 11工厂使用可变参数模板

时间:2017-07-25 09:10:01

标签: c++11 factory variadic-templates

我正在开发一个工厂,可以注册和创建具有不同参数类型和数字的类。我通过互联网搜索,我设法创建这个类:

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

这些寄存器对于参考和值参数都非常有效,但我无法使用创建者来设置它们。在我的调试过程中,我看到所有参数都是通过引用给出的,而不是通过值。

1 个答案:

答案 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

Live example

它需要转发重载到make_arg_vtable,就像我怀疑{{1}}那样。

总的来说,我认为这是一个坏主意,因为调用者只处理cv转换而没有别的。任何其他原因都会导致崩溃或其他未定义的行为。