针对 'kind' 层次结构使用可变参数验证 std::function
Validating std::function with variadic args against a 'kind' hierarchy
我有一个系统(C++14,使用 Visual Studio 2015 和 GCC 4.9.2),我们有许多不同类型的 'events' 可以导致回调发生,以及 classes 的层次结构,用于标识事件类型和特定于该类型事件的其他自定义属性。
有一个可以接收特定事件对象的事件管理器,以及一个在事件发生时调用的侦听器函数(订阅)。
有些事件会将某些参数传递给侦听器回调,但参数的类型取决于事件类型。
有没有一种方法可以验证传入的侦听器参数(可变参数)对于事件对象是否有效?
enum class EventKind {
first_kind, second_kind, third_kind
};
class EventBase {
public:
virtual EventKind kind() const = 0; // each subclass returns its EventKind identifier
virtual bool matches(const EventBase&) const = 0;
};
class FirstEvent : public EventBase {
public:
EventKind kind() const final { return EventKind::first_kind; }
bool matches(const EventBase&) const final; // compare kind and other properties
// other properties unique to FirstEvent
};
// other event subclasses ...
class EventManager {
public:
template<typename... Args>
void add_listener(const EventBase& event, std::function<void(Args...)> listener) {
// validation of Args... based on event.kind() goes here...
}
template<typename... Args>
trigger(const EventBase& event, Args... args) {
// called when event occurs
// internal lookup in the manager is done to find any listeners connected
// with this event object, and then we call it...
for (auto s : subscriptions[event.kind()]) {
if (event.matches(*(s->event))) {
auto* ss = dynamic_cast<Sub<Args...> *>(s);
if (ss && ss->listener) { ss->listener(args...); }
}
}
}
private:
struct SubBase {
EventBase* event;
};
template<typename... Args>
struct Sub : public SubBase {
std::function<void(Args...)> listener;
};
std::map<EventKind, std::vector<SubBase *>> subscriptions;
};
我已经有了工作代码,可以存储侦听器函数以供以后回调(与上面类似),但是 Args... 参数 pack/variadic 参数的匹配仅在当时完成该事件在事件管理器中触发 - 因此,当然,如果原始侦听器具有一组不匹配的参数,则不会调用它(并且有静默失败)。
能够在添加侦听器时根据事件类型验证此参数列表(希望以某种方式使用 class 层次结构)会很棒。有人有想法吗?
注意:我目前仅限于 C++14 / Visual Studio 2014 / gcc 4.9.2,因此不能使用任何 C++17 结构。
注册时需要具体的事件类型,并让事件提供监听器类型。
class FirstEvent : public EventBase {
public:
EventKind kind() const final { return EventKind::first_kind; }
bool matches(const EventBase&) const final; // compare kind and other properties
using listener_type = std::function<void(Foo, Bar, Baz)>;
};
template<typename Event>
using listener_t = typename Event::listener_type;
template<typename Event>
void EventManager::add_listener(listener_t<Event> listener) {
// can only be called with something that accepts the correct args
}
这是我设法开始工作的解决方案 - 它避免了事件 class 层次结构,但似乎工作正常。
// Validation of event listener args (general case)
template<typename... Args>
inline bool validate_event_listener_args(EventKind)
{ return false; }
// Validation of event listener args (no args case)
template<>
inline bool validate_event_listener_args<>(EventKind kind)
{
return (kind == EventKind::second_kind || ... );
}
// Validation of event listener args (for FirstEvent)
template<>
inline bool validate_event_listener_args<Foo, Bar, Qux>(EventKind kind)
{
return (kind == EventKind::first_kind);
}
// ...
然后我可以在 add_listener
中调用 validate_event_listener_args<Args...>(event.kind())
并在 returns 为假时抛出异常。这对于运行时验证很有用。
我有一个系统(C++14,使用 Visual Studio 2015 和 GCC 4.9.2),我们有许多不同类型的 'events' 可以导致回调发生,以及 classes 的层次结构,用于标识事件类型和特定于该类型事件的其他自定义属性。
有一个可以接收特定事件对象的事件管理器,以及一个在事件发生时调用的侦听器函数(订阅)。
有些事件会将某些参数传递给侦听器回调,但参数的类型取决于事件类型。
有没有一种方法可以验证传入的侦听器参数(可变参数)对于事件对象是否有效?
enum class EventKind {
first_kind, second_kind, third_kind
};
class EventBase {
public:
virtual EventKind kind() const = 0; // each subclass returns its EventKind identifier
virtual bool matches(const EventBase&) const = 0;
};
class FirstEvent : public EventBase {
public:
EventKind kind() const final { return EventKind::first_kind; }
bool matches(const EventBase&) const final; // compare kind and other properties
// other properties unique to FirstEvent
};
// other event subclasses ...
class EventManager {
public:
template<typename... Args>
void add_listener(const EventBase& event, std::function<void(Args...)> listener) {
// validation of Args... based on event.kind() goes here...
}
template<typename... Args>
trigger(const EventBase& event, Args... args) {
// called when event occurs
// internal lookup in the manager is done to find any listeners connected
// with this event object, and then we call it...
for (auto s : subscriptions[event.kind()]) {
if (event.matches(*(s->event))) {
auto* ss = dynamic_cast<Sub<Args...> *>(s);
if (ss && ss->listener) { ss->listener(args...); }
}
}
}
private:
struct SubBase {
EventBase* event;
};
template<typename... Args>
struct Sub : public SubBase {
std::function<void(Args...)> listener;
};
std::map<EventKind, std::vector<SubBase *>> subscriptions;
};
我已经有了工作代码,可以存储侦听器函数以供以后回调(与上面类似),但是 Args... 参数 pack/variadic 参数的匹配仅在当时完成该事件在事件管理器中触发 - 因此,当然,如果原始侦听器具有一组不匹配的参数,则不会调用它(并且有静默失败)。
能够在添加侦听器时根据事件类型验证此参数列表(希望以某种方式使用 class 层次结构)会很棒。有人有想法吗?
注意:我目前仅限于 C++14 / Visual Studio 2014 / gcc 4.9.2,因此不能使用任何 C++17 结构。
注册时需要具体的事件类型,并让事件提供监听器类型。
class FirstEvent : public EventBase {
public:
EventKind kind() const final { return EventKind::first_kind; }
bool matches(const EventBase&) const final; // compare kind and other properties
using listener_type = std::function<void(Foo, Bar, Baz)>;
};
template<typename Event>
using listener_t = typename Event::listener_type;
template<typename Event>
void EventManager::add_listener(listener_t<Event> listener) {
// can only be called with something that accepts the correct args
}
这是我设法开始工作的解决方案 - 它避免了事件 class 层次结构,但似乎工作正常。
// Validation of event listener args (general case)
template<typename... Args>
inline bool validate_event_listener_args(EventKind)
{ return false; }
// Validation of event listener args (no args case)
template<>
inline bool validate_event_listener_args<>(EventKind kind)
{
return (kind == EventKind::second_kind || ... );
}
// Validation of event listener args (for FirstEvent)
template<>
inline bool validate_event_listener_args<Foo, Bar, Qux>(EventKind kind)
{
return (kind == EventKind::first_kind);
}
// ...
然后我可以在 add_listener
中调用 validate_event_listener_args<Args...>(event.kind())
并在 returns 为假时抛出异常。这对于运行时验证很有用。