假设我有一个可以被其他对象观察到的对象:
struct Object
{
struct Listener
{
virtual void fire() = 0;
}
Object(std::vector<Listener *> &listeners) :
listeners_(listeners)
{}
void fire()
{
for(auto *l : listeners_)
l->fire();
}
private:
std::vector<Listener *> listeners_;
};
现在,我想使用模板做同样的事情。以下是我的意思:
template<typename ... Listeners>
struct Object
{
Object(Listeners&&...listeners)
{
// How do I store each of the differently-typed references?
}
void fire()
{
// How do I iterate over the list of listeners?
}
};
请注意,这里的关键是我试图避免虚拟函数调用。我不希望我的Listeners(在模板化代码中)必须为纯虚拟类或类似的类创建子类。
我建议不要这样做。你最初的设计似乎很适合有观察者——它很好地将设计的两个不同部分解耦。引入模板意味着每个都需要了解Object
拥有的所有侦听器。所以,要确保你真的想先做这件事。
也就是说,您正在寻找一个类型为std::tuple
的异构容器。存储只是:
template<typename... Listeners>
struct Object
{
std::tuple<Listeners...> listeners;
Object(Listeners const&... ls)
: listeners(ls...)
{ }
};
激发涉及使用index_sequence
技巧(此类仅在C++14中引入,但可以使用C++11实现)。这里有一个很好的答案来解释发生了什么。
public:
void fire() {
fire(std::index_sequence_for<Listeners...>{});
}
private:
template <size_t... Is>
void fire(std::index_sequence<Is...> ) {
using swallow = int[];
(void)swallow{0,
(void(std::get<Is>(listeners).fire()), 0)...
};
}
使用C++14通用Lambda,编写通用for_each_tuple
:更容易
template <class Tuple, class F, size_t... Is>
void for_each_tuple(Tuple&& tuple, F&& func, std::index_sequence<Is...> ) {
using swallow = int[];
(void)swallow{0,
(void(std::forward<F>(func)(std::get<Is>(std::forward<Tuple>(tuple)))), 0)...
};
}
template <class Tuple, class F>
void for_each_tuple(Tuple&& tuple, F&& func) {
for_each_tuple(std::forward<Tuple>(tuple), std::forward<F>(func),
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{}
);
}
现在你的fire()
变成了:
void fire() {
for_each_tuple(listeners, [](auto& l) { l.fire(); });
}
您可以使用std::tuple
来存储异构对象。
为了对其进行迭代,这里有一些模板魔术。这将为每个对象调用函子模板(因此,函子将适应不同的类型)。SFINAE用于确定何时停止迭代。变量From和To定义范围,每次迭代时From都会递增。当它们相等时,迭代必须停止,所以这就是为什么在这种情况下必须有一个空函数。
#include <tuple>
#include <type_traits>
#include <cstddef>
template <template <typename> class Functor, typename Tuple, std::size_t From, std::size_t To>
typename std::enable_if<From == To, void>::type for_each(const Tuple &t) {}
template <template <typename> class Functor, typename Tuple, std::size_t From = 0, std::size_t To = std::tuple_size<Tuple>::value>
typename std::enable_if<From < To, void>::type for_each(const Tuple &t) {
Functor<typename std::tuple_element<From, Tuple>::type> op;
op(std::get<From>(t));
for_each<Functor, Tuple, From + 1, To>(t);
}
您需要编写一个传递给它的函子模板,如:
template <typename T>
struct Functor {
void operator()(const T &x) {
// ...
}
}
将为每个对象调用。
在你的代码中,它会是这样的(未测试):
template <typename Listener>
struct FireFunctor {
void operator()(const Listener &x) {
x.fire();
}
}
template<typename ... Listeners>
struct Object
{
std::tuple<Listeners...> store;
Object(Listeners&&...listeners)
{
store = std::make_tuple(listeners...);
}
void fire()
{
for_each<FireFunctor>(store);
}
};