我有一个恼人的场景,我需要推迟某个对象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
的界面告诉用户 nothing 有关如何初始化context::_state
的信息。用户被迫查看initialize_state
的实现,然后查看state::state
以了解应将哪些内容传递给initialize_state
。
我可以将initialize_state
更改为...
void initialize_state(A&& a, B&& b, C&& c)
{
_state.emplace(std::move(a), std::move(b), std::move(c));
}
...但这有一个主要缺点:state::state
存在代码重复,需要手动维护,以防参数类型发生变化。
有什么方法可以让两全其美(DRY和用户友好的界面)?请注意,state
不可移动/可复制。
答案 0 :(得分:3)
课程state
可能无法复制/移动,但似乎A
,B
和C
是。 (所以我假设state
中有一些其他的内部数据阻止了可复制性/可移动性)
您可以将这些成员拉到另一个可以注入state
的类中。由于缺少更好的名称,我将其称为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));
}
};
答案 1 :(得分:2)
但这有一个主要缺点:与state :: state存在代码重复,需要手动维护以防参数类型发生变化。
这是封装的一般问题。这是(定义)不是DRY。
有一种方法可以保留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)...);
}
};
答案 2 :(得分:2)
可能创建的问题多于解决问题,您可以模拟您的课程:
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可以显示预期的参数)。