如何将下面的模板类推广到调用任何函数?

时间:2017-07-06 13:32:56

标签: c++ c++11 templates methods lambda

#include <map>
#include <functional>

template<typename... Args>
class Listner {
public:
    template<typename T>
    void attachListner(T *t) {
        m_listenerCallbacks.insert(std::make_pair(reinterpret_cast<void*>(t), [&t](Args... args){t->callback(args...); }));
    }

    template<typename T>
    void detachListner(T *t) {
        m_listenerCallbacks.erase(reinterpret_cast<void*>(t));
    }

    void notify(Args... args) {
        for (auto l : m_listenerCallbacks)
            l.second(args...);
    }
private:
    std::map<void*, std::function<void(Args...)>> m_listenerCallbacks;
};

使用上面的Listner类,我可以注册具有名为callback的公共方法的对象。是否可以通过方法名称(Listner)也可以作为模板参数传递的方式来概括callback类。

2 个答案:

答案 0 :(得分:1)

以下是基于可能适合您的示例代码的可能解决方案:

#include <map>
#include <functional>

template<typename>
struct Listner;

template<typename R, typename... Args>
struct Listner<R(Args...)> {
    template<typename T, R(T::*M)(Args...) = &T::callback>
    void attachListner(T *t) {
        m_listenerCallbacks.insert(std::make_pair(static_cast<void*>(t), [t](Args... args){ (t->*M)(args...); }));
    }

    void notify(Args... args) {
        for (auto l : m_listenerCallbacks)
            l.second(args...);
    }

private:
    std::map<void*, std::function<void(Args...)>> m_listenerCallbacks;
};

struct A {
    void f(int, char) {}
    void f(int, double) {}
    void f(int) {}
};

int main() {
    A a;
    Listner<void(int, char)> l;
    l.attachListner<A, &A::f>(&a);
    l.notify(0, 'c');
}

基本思想是你必须完全指定函数类型(参见Listner<void(int, char)>)。唯一的参数列表是不够的 我们可以通过类模板的部分特化来实现:

template<typename>
struct Listner;

template<typename R, typename... Args>
struct Listner<R(Args...)> {
    // ...
};

完成后,我们可以使用该类型将正确的重载检测为模板参数:

template<typename T, R(T::*M)(Args...) = &T::callback>
void attachListner(T *t) {
    // ...
}

请注意,我设置了默认值(&T::callback),以便您可以将其作为:

调用
l.attachListner<A, &A::f>(&a);

如果您想使用其他成员函数,或者:

l.attachListner(&a);

如果A有一个名为callback的成员方法,并且您希望像以前那样使用它。

答案 1 :(得分:0)

首先:我强烈建议按值传递t指针,而不是通过引用lambda函数;所以

m_listenerCallbacks.insert(
   std::make_pair(reinterpret_cast<void*>(t),i
                  [=](Args... args){t->callback(args...); }));
// ----------------^ (not &)

总之...

  

是否可以通过方法名称(回调)也可以作为模板参数传递的方式来推广Listner类。

作为attachListner()方法的类的模板参数?

在第一种情况下,我能想象的最好的(?)是一个基于C-macro的脏解决方案(我知道...... C-macro是邪恶的...但有时......)。

在以下示例中,假设您希望Listner在所有注册的对象中调用bar()方法;你必须按如下方式调用Listner

Listner(bar);

这将创建一个Listner_bar模板类,可以按如下方式使用

Listner_bar<int, std::string const &> l;

以下是一个完整的工作示例

#include <map>
#include <iostream>
#include <functional>

#define Listner(foo)                                                     \
                                                                         \
template <typename ... Args>                                             \
class Listner_##foo                                                      \
 {                                                                       \
   public:                                                               \
      template <typename T>                                              \
      void attachListner (T * t)                                         \
       { m_listenerCallbacks[reinterpret_cast<void*>(t)]                 \
            = [=](Args... args){(t->*(&T::foo))(args...);}; }            \
                                                                         \
      template <typename T>                                              \
      void detachListner (T * t)                                         \
       { m_listenerCallbacks.erase(reinterpret_cast<void*>(t)); }        \
                                                                         \
      void notify (Args ... args)                                        \
       { for (auto const & l : m_listenerCallbacks) l.second(args...); } \
                                                                         \
   private:                                                              \
      std::map<void*, std::function<void(Args...)>> m_listenerCallbacks; \
 }

struct A
 {
   void bar (int i, std::string const & s)
    { std::cout << "A::bar - " << i << ", " << s << std::endl; }
 };

struct B
 {
   void bar (int i, std::string const & s)
    { std::cout << "B::bar - " << i << ", " << s << std::endl; }
 };

Listner(bar);

int main ()
 {
   Listner_bar<int, std::string const &> l;

   A a;
   B b;

   l.attachListner(&a);
   l.attachListner(&b);

   l.notify(1, "one");

   l.detachListner(&a);

   l.notify(2, "two");
 }

如果你想将方法名称作为模板参数传递给attachListner()(因此不同对象的名称不同)...我猜想不是你想要的,但我能想到的最好的是传递指针方法到attachListner()(没有C风格的宏,这次);

之类的东西
l.attachListner(&a, &A::foo);

其中aA类型的对象,您希望将其称为foo()方法。

以下是一个完整的工作示例

#include <map>
#include <iostream>
#include <functional>

template <typename ... Args>
class Listner
 {
   public:
      template <typename T, typename M>
      void attachListner (T * t, M m)
       { m_listenerCallbacks[reinterpret_cast<void*>(t)]
            = [=](Args... args){(t->*m)(args...);}; }

      template <typename T>
      void detachListner (T * t)
       { m_listenerCallbacks.erase(reinterpret_cast<void*>(t)); }

      void notify (Args ... args)
       { for (auto const & l : m_listenerCallbacks) l.second(args...); }

   private:
      std::map<void*, std::function<void(Args...)>> m_listenerCallbacks;
 };

struct A
 {
   void foo (int i, std::string const & s)
    { std::cout << "A::foo - " << i << ", " << s << std::endl; }
 };

struct B
 {
   void bar (int i, std::string const & s)
    { std::cout << "B::bar - " << i << ", " << s << std::endl; }
 };


int main ()
 {
   Listner<int, std::string const &> l;

   A a;
   B b;

   l.attachListner(&a, &A::foo);
   l.attachListner(&b, &B::bar);

   l.notify(1, "one");

   l.detachListner(&a);

   l.notify(2, "two");
 }

- 编辑 - 正如skypjack所指出的,如果调用的方法没有超载,则第二个解决方案会起作用。 skypjack的答案提出了解决这个问题的方法。