实现类似于Qt的Signal和Slot通讯机制

Signal和Slot机制

其是qt提供的对象间通讯机制。

实现细节

思路

实现类似于信号与槽的机制,无非就是做一些薄记工作,qt中通过虚函数和moc来实现。那我们怎么实现?我的思路是signal是一个对象,其他的slot将自己的回调注册进signal对象中,signal完成薄记工作。那么需要实现的细节归于一下几点。
1. 参数的提取与匹配。
2. slot容器的制作。
3. slot的调用。

参数处理

怎么处理参数是一大难题。我们必须推导出各类型,包括类的类型和参数类型,在此我们引入很多特性模板类。

template <typename T>
struct TypeTraits
{
    typedef T type_value;
    typedef T& refType_value;
    typedef const T& constRefType_value;
    typedef T* pointerType_value;
    typedef const T* constPointerType_value;
};

//>>>
template <typename Re>
struct ParameterTraits
{
    static const int size = -1;
};

template <typename Re>
struct ParameterTraits<Re (*)()>
{
    static const int size = 0;
};

template <typename Re, typename T0>
struct ParameterTraits<Re (*)(T0)>
{
    static const int size = 1;
    typedef T0 P0;
};
.......
//>>>>
template <typename Class, typename Re>
struct ParameterTraits<Re (Class::*)()>
{
    static const int size = 0;

    typedef Class classType_value;

};

template <typename Class, typename Re, typename T0>
struct ParameterTraits<Re (Class::*)(T0)>
{
    static const int size = 1;

    typedef T0 P0;

    typedef Class classType_value;

};

template <typename Class, typename Re, typename T0, typename T1>
struct ParameterTraits<Re (Class::*)(T0, T1)>
{
    static const int size = 2;

    typedef T0 P0;
    typedef T1 P1;

    typedef Class classType_value;
};

上述模板类作用在于提取c-style function和c++类成员函数的类类型和参数类型。

//>>>
template<int Size, typename T>
struct ParameterTupleTraitsProxy
{

};

template<typename T>
struct ParameterTupleTraitsProxy<0, T>
{
    typedef std::tuple<> Parameters;
};

template<typename T>
struct ParameterTupleTraitsProxy<1, T>
{
    typedef std::tuple<typename T::P0> Parameters;
};

template<typename T>
struct ParameterTupleTraitsProxy<2, T>
{
    typedef std::tuple<typename T::P0, typename T::P1> Parameters;
};
........
template <typename ParameterTraits>
struct ParameterTupleTraits
{
    typedef typename ParameterTupleTraitsProxy<ParameterTraits::size, ParameterTraits>::Parameters Parameters;
};

上述用来保存signal发送时,参数的保存,传递到slot处的模板类,使用了c++0x的Tuple。

slot

template <typename Paras>
struct SlotBase
{
    virtual ~SlotBase() = 0;
    virtual void dotask(Paras paras) = 0;
};

template <typename Paras>
SlotBase<Paras>::~SlotBase()
{

}

template <typename M>
struct Slot:public SlotBase<typename ParameterTupleTraits<ParameterTraits<M> >::Parameters>
{

    typedef typename TypeTraits<typename ParameterTraits<M>::classType_value>::pointerType_value type_value;
    typedef typename ParameterTupleTraits<ParameterTraits<M> >::Parameters Parameters;
    Slot(type_value sloter, M method):m_object(sloter), m_method(method)
    {

    }

    void dotask(Parameters paras)
    {
        impleDoTask(m_object, m_method, paras);
    }

    type_value m_object;
    M m_method;
};

SlotBase为做容器的基类,dotask函数完成实际的函数调用。Slot保存调用对象和成员函数指针,实现实际的调用,但是参数个数是不同的,这个需要引入对个个参数调用的特例。如下:

template <typename Class, typename Method>
void impleDoTask(Class *c, Method m, std::tuple<> p)
{
    (c->*m)();
}

template <typename Class, typename Method, typename T0>
void impleDoTask(Class *c, Method m,  std::tuple<T0> p)
{
    (c->*m)(std::get<0>(p));
}
template <typename Class, typename Method, typename T0, typename T1>
void impleDoTask(Class *c, Method m,  std::tuple<T0, T1> p)
{
    (c->*m)(std::get<0>(p), std::get<1>(p));
}

template <typename Class, typename Method, typename T0, typename T1, typename T2>
void impleDoTask(Class *c, Method m,  std::tuple<T0, T1, T2> p)
{
    (c->*m)(std::get<0>(p), std::get<1>(p), std::get<2>(p));
}


template <typename Class, typename Method, typename T0, typename T1, typename T2, typename T3>
void impleDoTask(Class *c, Method m,  std::tuple<T0, T1, T2, T3> p)
{
    (c->*m)(std::get<0>(p), std::get<1>(p), std::get<2>(p), std::get<3>(p));
}

其对不同个数参数提供具体的模板特化。

Signal

template <typename Method>
struct Signal
{
    ~Signal()
    {
        for (typename listValue_type::iterator ite = sloters.begin(); ite != sloters.end(); ++ite)
        {
            delete (*ite);
        }
        sloters.clear();
    }

    template <typename M>
    bool connect(typename Slot<M>::type_value object, M method)
    {
        typename listValue_type::iterator ite = std::find_if(sloters.begin(), sloters.end(), FindHelper<M>(object, method));
        if (ite != sloters.end()) {
            return false;
        }
        sloters.push_back(new Slot<M>(object, method));
        return true;
    }

    template <typename M>
    bool disconnect(typename Slot<M>::type_value object, M method)
    {
        typename listValue_type::iterator ite = std::find_if(sloters.begin(), sloters.end(), FindHelper<M>(object, method));
        if (ite != sloters.end()) {
            delete (*ite);
            sloters.erase(ite);
            return true;
        }
        return false;
    }

    void eemit()
    {
        std::tuple<> para;
        for (typename listValue_type::iterator ite = sloters.begin(); ite != sloters.end(); ++ite)
        {
            (*ite)->dotask(para);
        }
    }

    template <typename T0>
    void eemit(T0 t)
    {
        std::tuple<T0> para = std::make_tuple(t);
        for (typename listValue_type::iterator ite = sloters.begin(); ite != sloters.end(); ++ite)
        {
            (*ite)->dotask(para);
        }
    }

    template <typename T0, typename T1>
    void eemit(T0 t, T1 t1)
    {
        std::tuple<T0, T1> para = std::make_tuple(t, t1);
        for (typename listValue_type::iterator ite = sloters.begin(); ite != sloters.end(); ++ite)
        {
            (*ite)->dotask(para);
        }
    }

    template <typename T0, typename T1, typename T2>
    void eemit(T0 t, T1 t1, T2 t2)
    {
        std::tuple<T0, T1, T2> para = std::make_tuple(t, t1, t2);
        for (typename listValue_type::iterator ite = sloters.begin(); ite != sloters.end(); ++ite)
        {
            (*ite)->dotask(para);
        }
    }
    ......
     //使用Method来实例化list,是为了在connect的时候,防止Method和M不一致,也就是参数不一致。
    typedef std::list<SlotBase<typename ParameterTupleTraits<ParameterTraits<Method> >::Parameters>* > listValue_type;
    listValue_type sloters;

    template <typename T>
    struct FindHelper
    {
        FindHelper(typename Slot<T>::type_value object, T method):m_object(object),m_method(method)
        {

        }

        bool operator()(const typename listValue_type::value_type& val) const
        {
            const Slot<T> *p = static_cast<Slot<T>*>(val);
            return p->m_object == m_object && p->m_method == m_method;
        }

        bool operator()(typename listValue_type::value_type& val)
        {
            const Slot<T> *p = static_cast<Slot<T>*>(val);
            return p->m_object == m_object && p->m_method == m_method;
        }

        typename Slot<T>::type_value m_object;
        T m_method;

    };


};

sloters完成了对各个sloter的保存。connect和disconnect实现了信号连接和断开,eemit实现了信号的发送,其也提供不同个数的参数的模板特化。FindHelper用来防止一个对象重复连接。
例子

#include <iostream>
#include "signal.h"
using namespace std;

struct TTT
{
    int a;
    int b;
    int c;
};

class N
{
public:
    virtual void func_c(int a, int b) = 0;

};

class A : public N
{
public:
    A() {}
    void func_b(int a) {cout << a << "aaaaaa\n";}
    void func_c(int a, int b) {cout << a << "+" << b << "=" << a+b << std::endl;}
    void func_a()
    {
        TTT t = {1, 's', 't'};
        s.eemit(t, t, t);
    }

    void func_z()
    {
        cout << "zhou xiang ";
    }

    Signal<void (*)(TTT, TTT, TTT)> s;

};

class B
{
public:
    B(A *a):m_a(a){}
    void func_b(int a) {cout << a << "bbbbbbb\n";}
    void func_slot(TTT t, TTT t2, TTT t3)
    {
        cout << t.a + t2.b + t3.c << "-==-=-=-==-=\n";
    }

    void func_z()
    {
        cout << "love chenchen\n";
    }

    A *m_a;
    void func_e()
    {
        m_a->s.connect(this, &B::func_slot);
    }
};



int main()
{
    A object;
    B object1(&object);

    Signal<void (*)(int, int)> s;

    Signal<void (*)()> ssss;

    ssss.connect(&object, &A::func_z);
    ssss.connect(&object1, &B::func_z);
    ssss.connect(&object1, &B::func_z);

// s.connect(&object ,&A::func_c);
// s.connect(&object ,&A::func_c);
// s.connect(&object ,&A::func_c);
// s.connect(&object ,&A::func_c);
// s.connect(&object ,&A::func_c);
// s.connect(&object ,&A::func_c);
// s.connect(&object ,&A::func_c);
// object1.func_e();
// object1.func_e();

// object.func_a();

    ssss.eemit();
    ssss.disconnect(&object, &A::func_z);
    ssss.eemit();


    cout << "---------------------------\n";


    s.eemit(4, 456);



    return 0;
}

这是我测试的例子。

这个只是一个雏形,多线程不能直接dotask,应该将调用投递到对象所在线程。

代码地址:https://git.oschina.net/zhouX/signal.git

你可能感兴趣的:(C++,模板,Signal,面向对象编程,SLOT)