C++中实现回调机制的几种方式一文中,我们提到了实现回调的三种方式(C风格的回调函数, Sink方式和Delegate方式)。在面向对象开发中,delegate的方式是最灵活和方便的,因此很早就有人用复杂的模板去模拟(有兴趣的话可以看这里这里),总之是实现起来很复杂。但是现在借助C++11的functionbind, 我们可以很方便的去实现。下面是我自己的一种实现方式:
  namespace Common
{
    typedef void* cookie_type;

    template<typename TR, typename T1, typename T2>
    class CEvent
    {
    public:
        typedef TR return_type;
        typedef T1 first_type;
        typedef T2 second_type;

        typedef std::function<return_type (first_type, second_type)> handler_type;

        ~CEvent()
        {
            Clear();
        }

        return_type operator()(first_type p1, second_type p2)
        {
            return_type ret = return_type();
            size_t size = _handlers.size();
            for(size_t i=0; i<size; ++i)
            {
                ret = _handlers[i]->operator()(p1, p2);
            }
            return ret;
        }

        cookie_type AddHandler(std::function<return_type (first_type, second_type)> h)
        {
            CEventHandler*p = new(nothrow)  CEventHandler(h);
            if(p != nullptr) _handlers.push_back(p);
            return (cookie_type)p;
        }

        template<typename class_type, typename class_fun>
        cookie_type AddHandler(class_type* pThis, class_fun f)
        {
            CEventHandler* p = new(nothrow) CEventHandler(pThis, f);
            if(p != nullptr) _handlers.push_back(p);
            return (cookie_type)p;
        }

        void RemoveHandler(cookie_type cookie)
        {
            CEventHandler* p = (CEventHandler*)cookie;

            auto itr = std::find(_handlers.begin(), _handlers.end(), p);
            if(itr != _handlers.end())
            {
                _handlers.erase(itr);
                delete p;
            }
            else
            {
                assert(false);
            }
        }

        void Clear()
        {
            if(!_handlers.empty())
            {
                int n = _handlers.size();
                std::for_each(_handlers.begin(), _handlers.end(), [](CEventHandler* p)
                { 
                    assert(p != nullptr);
                    delete p;
                });
                _handlers.clear();        
            }
        }

    private:
        class CEventHandler 
        {
        public:
            CEventHandler(handler_type h)
            {
                _handler = h;
                assert(_handler != nullptr);
            }

            template<typename class_type, typename class_fun>
            CEventHandler(class_type* pThis, class_fun object_function)
            {
                using namespace std::placeholders;
                _handler = std::bind(object_function, pThis, _1, _2);
                assert(_handler != nullptr);
            }

            return_type operator()(first_type p1, second_type p2)
            {
                return_type ret = return_type();
                assert(_handler != nullptr);
                if(_handler != nullptr) ret = _handler(p1, p2);
                return ret;
            }

            handler_type _handler;
        };


    private:
        std::vector<CEventHandler*> _handlers;
    };
}

大概实现思想是我们通过一个内置的CEventHandler 类来封装处理函数,我们可以通过AddHandler来添加事件处理函数,添加时会返回一个Cookie,我们可以通过该Cookie来RemoveHandler, 下面是测试代码:
#include "stdafx.h"
#include <iostream>
#include "event1.h"

using namespace std;

class CObjectX 
{

};

class CClickEventArgs: public CObjectX
{

};


class CButton: public CObjectX
{
public:
    void FireClick()
    {
        CClickEventArgs args;
        OnClicked(this, args);
    }

    Common::CEvent<int, CObjectX*, CClickEventArgs&> OnClicked;
};


class CMyClass 
{
public:
    int OnBtuttonClicked(CObjectX* pButton, CClickEventArgs& args)
    {
        cout << "CMyClass: Receive button clicked event" << endl;
        return 1;
    }
};

int OnBtuttonClicked_C_fun(CObjectX* pButton, CClickEventArgs& args)
{
    cout << "C Style Function: Receive button clicked event" << endl;
    return 1;
}


class CMyFunObj
{
public:
    int operator()(CObjectX* pButton, CClickEventArgs& args)
    {
        cout << "Functor: Receive button clicked event" << endl;
        return 1;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    using namespace std::placeholders;

    CButton btn;

    CMyClass obj;
    Common::cookie_type c1 = btn.OnClicked.AddHandler(&obj, &CMyClass::OnBtuttonClicked);

    Common::cookie_type c2 = btn.OnClicked.AddHandler(OnBtuttonClicked_C_fun);

    CMyFunObj functor;
    Common::cookie_type c3 = btn.OnClicked.AddHandler(functor);

    btn.FireClick();


    btn.OnClicked.RemoveHandler(c2);

    std::cout << endl;


    btn.FireClick();

    system("pause");

    return 0;
}

以下是测试结果:


 可以看到, 我们在普通C函数, 类成员函数和仿函数(functor)中都测试通过。

另外对于事件函数返回值为void的情况,会编译出错,我们需要偏特化一下:
    template< typename T1, typename T2>
    class CEvent<void, T1, T2>
    {
    public:
        typedef void return_type;
        typedef T1 first_type;
        typedef T2 second_type;

        typedef std::function<return_type (first_type, second_type)> handler_type;

        ~CEvent()
        {
            Clear();
        }

        return_type operator()(first_type p1, second_type p2)
        {
            size_t size = _handlers.size();
            for(size_t i=0; i<size; ++i)
            {
                _handlers[i]->operator()(p1, p2);
            }
        }

        cookie_type AddHandler(std::function<return_type (first_type, second_type)> h)
        {
            CEventHandler*p = new(nothrow)  CEventHandler(h);
            if(p != nullptr) _handlers.push_back(p);
            return (cookie_type)p;
        }

        template<typename class_type, typename class_fun>
        cookie_type AddHandler(class_type* pThis, class_fun f)
        {
            CEventHandler* p = new(nothrow) CEventHandler(pThis, f);
            if(p != nullptr) _handlers.push_back(p);
            return (cookie_type)p;
        }

        void RemoveHandler(cookie_type cookie)
        {
            CEventHandler* p = (CEventHandler*)cookie;

            auto itr = std::find(_handlers.begin(), _handlers.end(), p);
            if(itr != _handlers.end())
            {
                _handlers.erase(itr);
                delete p;
            }
            else
            {
                assert(false);
            }
        }

        void Clear()
        {
            if(!_handlers.empty())
            {
                int n = _handlers.size();
                std::for_each(_handlers.begin(), _handlers.end(), [](CEventHandler* p)
                { 
                    assert(p != nullptr);
                    delete p;
                });
                _handlers.clear();        
            }
        }

    private:
        class CEventHandler 
        {
        public:
            CEventHandler(handler_type h)
            {
                _handler = h;
                assert(_handler != nullptr);
            }

            template<typename class_type, typename class_fun>
            CEventHandler(class_type* pThis, class_fun object_function)
            {
                using namespace std::placeholders;
                _handler = std::bind(object_function, pThis, _1, _2);
                assert(_handler != nullptr);
            }

            return_type operator()(first_type p1, second_type p2)
            {
                assert(_handler != nullptr);
                if(_handler != nullptr) _handler(p1, p2);
            }

            handler_type _handler;
        };


    private:
        std::vector<CEventHandler*> _handlers;
    };

最后谈一下在写这个代码中遇到的问题:
(1)不知道你能不能发现下面代码的问题, 我在写代码时就栽在这里了:
     vector<int*>  v;
    int* p1 = new int(1);
    v.push_back(p1);
    int* p2 = new int(2);
    v.push_back(p2);
 
    //尝试删除所有值为p1的项
    //由该代码想到=>v.erase(std::remove(v.begin(), v.end(), p1), v.end());
    //错误代码:
    auto itr = remove(v.begin(), v.end(), p1);
    for_each(itr, v.end(), [](int* p){delete p;});
    v.erase(itr, v.end());

   //正确代码:
   v.erase(remove_if(v.begin(), v.end(), [](int*p)->bool {if(p==p1) {delete p; return true;} else return false}), v.end());

(2)我们想把cookei_type放到类里面去, 类似这样:
1     template<typename TR, typename T1, typename T2>
2     class CEvent
3     {
4     public:
5         typedef TR return_type;
6         typedef T1 first_type;
7         typedef T2 second_type;
8         typedef void* cookie_type;

可发现要这样使用:
Common::CEvent<int, CObjectX*, CClickEventArgs&>::cookie_type c1 = btn.OnClicked.AddHandler(&obj, &CMyClass::OnBtuttonClicked);
太不方便了, 不知道大家有没有好的方法。

注:上面的代码还没有经过真正商业使用,如果有问题欢迎指出。
posted on 2013-01-31 14:16 Richard Wei 阅读(10239) 评论(8)  编辑 收藏 引用 所属分类: C++

FeedBack:
# re: 在C++中实现事件(委托)
2013-01-31 14:45 | lierlier
有返回值的事件是需要使用返回策略的,单纯返回最后一个值由于回调绑定到事件顺序是不确定的,所以实际上无意义  回复  更多评论
  
# re: 在C++中实现事件(委托)
2013-01-31 14:49 | Richard Wei
@lierlier
确实, 所以.Net里的事件返回类型都都void。这里带返回值的事件适用于只有一个函数绑定。  回复  更多评论
  
# re: 在C++中实现事件(委托)
2013-01-31 16:56 | lierlier
如果不要求判断函数是否已经绑定过了,那么有个简单的做法

这里保持bind在外边可以保留灵活性,比如把三个参数的事件处理函数加上一个默认参数绑定到两个参数的事件上。另外把Event定义为
template<class A1 = NullT, class A2 = NullT,...>
这样的形式,利用偏特化,就可以支持任意个数的事件参数

代码:
#include <map>
#include <functional>

using namespace std;


template<class Arg1, class Arg2>
class Event
{
typedef void HandlerT(Arg1, Arg2);
int m_handlerId;

public:
Event() : m_handlerId(0) {}

template<class FuncT>
int addHandler(FuncT func)
{
m_handlers.emplace(m_handlerId, forward<FuncT>(func));
return m_handlerId++;
}

void removeHandler(int handlerId)
{
m_handlers.erase(handlerId);
}

void operator ()(Arg1 p1, Arg2 p2)
{
for ( const auto& i : m_handlers )
i.second(p1, p2);
}

private:
map<int, function<HandlerT>> m_handlers;
};

void f1(int, int)
{
printf("f1()\n");
}

struct F2
{
void f(int, int)
{
printf("f2()\n");
}

void operator ()(int, int)
{
printf("f3()\n");
}
};

int _tmain(int argc, _TCHAR* argv[])
{
Event<int, int> e;

int id = e.addHandler(f1);

e.removeHandler(id);

using namespace std::placeholders;

F2 f2;

e.addHandler(bind(&F2::f, f2, _1, _2));
e.addHandler(bind(f2, _1, _2));

e.addHandler([](int, int) {
printf("f4()\n");
});

e(1, 2);

return 0;
}  回复  更多评论
  
# re: 在C++中实现事件(委托)
2013-01-31 17:36 | Richard Wei
@lierlier
多谢指点,感觉你的实现方式对外界比较灵活,性能也比较高。
而我的实现更强调封装,适合于在某个框架中使用。  回复  更多评论
  
# re: 在C++中实现事件(委托)
2013-02-01 16:49 | zdhsoft
如果我直接使用bind,是不是明显比你的灵活多了?初步看了一下,楼主只是在bind上加了一个外壳封装而已。真正委托的实现,还是交给了bind  回复  更多评论
  
# re: 在C++中实现事件(委托)
2013-02-01 17:05 | Richard Wei
@zdhsoft
恩,本身就是借助c++11的 function和bind进行封装  回复  更多评论
  
# re: 在C++中实现事件(委托)
2013-02-07 13:32 | Jeff Chen
我有两个问题:
1、为何不使用boost的signal呢?
2、您的方案与signal相比,有哪些优点,哪些缺点呢?  回复  更多评论
  
# re: 在C++中实现事件(委托)
2013-02-07 17:03 | Richard Wei
@Jeff Chen
惭愧, 没研究过boost。
可能优点就是简单,可以自己掌控吧,实际上好多公司是禁用boost的。  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理