代码之家  ›  专栏  ›  技术社区  ›  Vittorio Romeo

在完美转发函数中公开参数类型,避免代码重复

  •  11
  • Vittorio Romeo  · 技术社区  · 7 年前

    我有一个恼人的场景,我需要推迟某个对象的初始化 state 并允许用户按需构建一个。E、 g。

    // user code
    
    context c;
    // ...do something...
    c.initialize_state(a, b, c);
    

    // library code
    
    class context
    {
    private:
        class state
        {
            state(A a, B b, C c);
    
            state(const state&) = delete;
            state(state&&) = delete;
        };
    
        std::optional<state> _state; // or `boost::optional`
    
    public:
        template <typename... Xs>
        void initialize_state(Xs&&... xs) 
        {
            _state.emplace(std::forward<Xs>(xs)...);
        }
    };
    

    从上面的代码中可以看到 context::initialize_state 告诉用户 没有什么 关于如何初始化 context::_state 。用户必须查看 initialize_state 然后看看 state::state 了解应该传递给什么 初始化\u状态

    我可以改变 初始化\u状态

    void initialize_state(A&& a, B&& b, C&& c) 
    {
        _state.emplace(std::move(a), std::move(b), std::move(c));
    }
    

    。。。但这有一个主要缺点:与 状态::状态 ,需要手动维护,以防参数类型更改。

    有什么方法可以让我做到两全其美(干净利落和用户友好的界面)?请注意 状态 不可移动/复制。

    3 回复  |  直到 7 年前
        1
  •  3
  •   AndyG    7 年前

    班级 state 可能无法复制/移动,但似乎 A ,则, B C 是(所以我假设 状态 防止可复制性/可移动性)

    您可以将这些成员拉入另一个可以注入的类中 状态 .没有更好的名字,我就叫它 state_args :

    struct state_args
    {
       explicit state_args(A a, B b, C c);
       A a_;
       B b_;
       C c_;
    };
    

    可实现以下功能:

    class context
    {
    private:
        class state
        {
            state(state_args args);
    
            state(const state&) = delete;
            state(state&&) = delete;
        };
    
        std::optional<state> _state; // or `boost::optional`
    
    public:
        template<class STATE_ARGS, /*enable_if to ensure STATE_ARGS is indeed state_args*/>
        void initialize_state(STATE_ARGS&& internal_state) 
        {
            _state.emplace(std::forward<STATE_ARGS>(internal_state));
        }
    };
    
        2
  •  2
  •   Richard Hodges    7 年前

    但这有一个主要缺点:state::state存在代码重复,需要手动维护,以防参数类型更改。

    这是封装的一般问题。它(定义)不干燥。

    有一种方法可以保持 state 构造函数重载和 initialize_state ,即使用 enable_if 以及 is_constructible 类型特征。

    class context
    {
    private:
        class state
        {
        public:
            state(A a, B b, C c);
    
            state(const state&) = delete;
            state(state&&) = delete;
        };
    
        std::optional<state> _state; // or `boost::optional`
    public:
        template <typename... Xs>
        auto 
        initialize_state(Xs&&... xs) 
        -> 
        std::enable_if_t
        <
            // condition
            std::is_constructible<state, Xs...>::value, 
    
            // return type
            void
        >
        {
            _state.emplace(std::forward<Xs>(xs)...);
        }
    };
    
        3
  •  2
  •   Jarod42    7 年前

    可能会产生比解决问题更多的问题,您可以模板化您的类:

    template <typename ... Ts>
    class context_impl
    {
    private:
        class state
        {
            state(Ts...);
    
            state(const state&) = delete;
            state(state&&) = delete;
        };
    
        std::optional<state> _state; // or `boost::optional`
    
    public:
        void initialize_state(Ts&&... xs) 
        {
            _state.emplace(std::forward<Xs>(xs)...);
        }
    };
    
    using context = context_impl<A, B, C>;
    

    由于模板是由类固定的, void initialize_state(Ts&&... xs) 具有固定签名(例如,intellisense可以显示预期的参数)。