Qt信号槽实现(高仿,三个版本)

一、写在前面

最近由于工作的需要,想着使用Qt的信号槽机制,但又不依赖Qt,所以想着实现一下,不过在这里要说明:
1. 在Boost库中是有信号槽的如果你不想太麻烦,可以直接使用这里提供的功能
2. Qt的信号槽实现机制是很复杂的,我们之所以可以使用Qt的关键字来实现相关功能,是因为qt有自己的预处理器,在编译的时候预处理器会帮助我们完成很多事情,而这些事情不用我们去关心,关于Qt的预处理器目前没有太多的了解,这里不做讲解,在这里推荐先看看 这篇博客(很不错!) ,这篇博客写的很好,给了我很大的启发

二、实现

凡事都是有简单到复杂的,所以这里给出我的几个版本,希望对大家有帮助,在网上也有很多前辈对这一功能进行了自己的实现,各有千秋,所以可以自行百度查找,这里不再一一列出,当然如果时间和经历允许,可以通过研究qt的源代码来学习,qt的源代码还是很有意思的(我只看来一小部分)。我一共写了三个版本,下面依次介绍不同版本,每个版本的所有代码粘贴在一个文件中就可以运行了,所以我就不放全家福了

版本1无参信号槽


首先来实现一个不含参数的信号槽连接,实现简单的回调,但是在这个简单的模式当中不会对信号槽的处理进行隐藏,处理函数需要手动补全,在版本3中会对这部分进行隐藏,不要急~这个版本只是为了初步理顺处理流程!!!
1. 头文件

#include <vector>
#include <iostream>


2. 为了存放每一对信号槽匹配关系,所以需要建立一个容器,由于具体槽的类型不一样,所以通过一个父类来隐藏子类差异,首先给出这一父类,其中有成员变量id,通过此id来找到不同的连接对,是不同连接对的唯一标识,这一对象应该对每一个具体对象来管理,可以按照id来控制触发顺序

class EmelemtBase
{
public:
    virtual void HitTarget() = 0;
    unsigned int GetId() { return id; }

    unsigned int id;
};


3. 下面来创建存储容器,容器继承自ElementBase,在这个里面来存放具体的信号槽以及信号和槽所属的对象,记录的内容一共四项

template<typename C1,typename T1,typename C2,typename T2>
class Element:public EmelemtBase
{
public:
    virtual void HitTarget()
    {
        (targetBody->*targetPtr)();
    }
    virtual void SetTarget(T1 s, C2 tar,T2 p, const unsigned int& i)
    {
        sourcePtr = s;
        targetBody = tar;
        targetPtr = p;
        id = i;
    }

    C1 sourceBody; //信号主体
    T1 sourcePtr; //存储信号函数指针
    C2 targetBody; //目标主体
    T2 targetPtr; //存储目标函数指针
};


4. 为了实现信号槽,要求所有对象都要记录属于自己的信号槽记录表单,所有创建公共父类,在父类中通过一个vector来存储该对象的信号槽表单

/*
对象的基类,所有的对象从这一父类进行派生,用来统一信号槽的后台存储
*/
class ThreadBase
{
public:
    ThreadBase() {}
    virtual ~ThreadBase()
    {
        std::vector<EmelemtBase*>::iterator iter = ElementManager.begin();
        for (; iter != ElementManager.end(); ++iter)
        {
            if ((*iter) != nullptr)
            {
                delete (*iter);
                (*iter) = nullptr;
            }
        }
    }

    std::vector<EmelemtBase*> ElementManager;
};


5. 这时我们可以对连接函数进行实现,这里写一个模板函数,接口中包含5个参数

template<typename C1, typename T1, typename C2, typename T2>
void myconnect(C1 obj1, T1 signal, C2 obj2, T2 slot,unsigned int id)
{
    Element<C1,T1,C2,T2>* tempElement = new Element<C1, T1, C2, T2>();
    EmelemtBase* tempElementPtr = tempElement;

    tempElement->SetTarget(signal,obj2,slot, id);
    obj1->ElementManager.push_back(tempElementPtr);
}
/*连接函数,改变接口,这一对象在后面的版本中会十分重要,在这里很鸡肋*/
#define CONNECT(obj1,signal, obj2, slot, id) \
            myconnect(obj1,signal, obj2, slot, id);


6. 实例化一个信号类,这里是最初的,所以不对信号处理进行隐藏封装,调用信号实际上是触发一个函数,这个函数体中实现了处理方法,不过这是最初的,不是最终的!!!不然你写一个信号,就得写一个处理方法,岂不是很累?

/*第一种对象*/
class Unit1 :public ThreadBase
{
public:
    void signal1()
    {
        std::vector<EmelemtBase*>::const_iterator iter = ElementManager.begin();
        for (;iter != ElementManager.end();++iter)
        {
            if ((*iter)->GetId() == 1)
            {
                (*iter)->HitTarget();
            }
        }
    }
    void run()
    {
        signal1();
    }
};


7. 实例化一个槽类,槽不需要实现相应处理方法,因为这是被调用的,所以直接正常写就行了。

/*第二种对象*/
class Unit2 :public ThreadBase
{
public:
    void slot1()
    {
        std::cout << "hited" << std::endl;
    }
};


8. 最给个main函数示例,第一版就结束啦~运行结束是在控制台上输出hited

int main()
{
    Unit1 obj1;
    Unit2 obj2;

    CONNECT(&obj1, &Unit1::signal1, &obj2, &Unit2::slot1,1);
    obj1.run();
    system("pause");
    return 0;
}

版本2——含参数传递

在这个版本中要实现一个可以传递参数的信号槽,由于函数参数类型是不确定的,所以这里使用一个any类型(boost库中有),这里我用的是我学习时候写的一个自己的MyAny类型,实现原理参考boost库中的any类型
1. 先给头文件

扫描二维码关注公众号,回复: 2891767 查看本文章
#include <vector>
#include <iostream>
#include <stdarg.h>
//----------------------
#include "MyAny.h"
//-----------------------


2. 连接基类,同版本1

class EmelemtBase
{
public:
    virtual void* HitTarget(std::vector<any>& pl) = 0;
    unsigned int GetId() { return id; }

    unsigned int id;
};


3. 连接记录容器,同版本1

template<typename C1, typename T1, typename C2, typename T2>
class Element :public EmelemtBase
{
public:
    virtual void* HitTarget(std::vector<any>& pl)
    {
        (targetBody->*targetPtr)(pl);
        return nullptr;
    }

    virtual void SetTarget(T1 s, C2 tar, T2 p, const unsigned int& i)
    {
        sourcePtr = s;
        targetBody = tar;
        targetPtr = p;
        id = i;
    }

    C1 sourceBody;
    T1 sourcePtr;
    C2 targetBody;
    T2 targetPtr;
};
//=================================================================================


4. 对象基类,同版本1

class ThreadBase
{
public:
    ThreadBase() {}
    virtual ~ThreadBase()
  {
        std::vector<EmelemtBase*>::iterator iter = ElementManager.begin();
        for (; iter != ElementManager.end(); ++iter)
        {
            if ((*iter) != nullptr)
            {
                delete (*iter);
                (*iter) = nullptr;
            }
        }
     }

    std::vector<EmelemtBase*> ElementManager;
};


5. 连接函数,同版本1

template<typename C1, typename T1, typename C2, typename T2>
void myconnect(C1 obj1, T1 signal, C2 obj2, T2 slot, unsigned int id)
{
    Element<C1, T1, C2, T2>* tempElement = new Element<C1, T1, C2, T2>();
    EmelemtBase* tempPtr = tempElement;

    tempElement->SetTarget(signal, obj2, slot, id);
    obj1->ElementManager.push_back(tempPtr);

}
//=================================================================================


6. 连接接口宏封装,不同与版本1!!! 在这里我们对槽的处理进行隐藏,在这里,通过MySLOT宏来声明一个函数可以作为这个类的槽来使用,实际上是通过这个宏来创建一个*hide函数,这一函数是对参数进行转换,参数在信号和槽之间的传递,首先信号将参数装入一个any类型容器,传给隐藏槽,隐藏槽将参数转化成具体类型,在传给具体槽,这样隐藏槽可通过宏来展开,不用反复书写,只需手动声明即可,在MySLOT宏中需要传入两个参数,槽名和参数类型(这里只允许一个参数)。此外要注意CONNECT宏的书写,很关键的哦~

#define CONNECT(obj1,signal, obj2, slot, id) \
            myconnect(obj1,signal, obj2, slot##Hide, id);
//=================================================================================
#define MySLOT(FunName,ParameterType) \
inline void FunName##Hide (std::vector<any>& pl)\
{\
    FunName(any_cast<ParameterType>(pl.at(0))); \
}
//=================================================================================


7. 信号类,现在signal可以带有一个自己的参数了,不过信号体依然是裸露的,没有进行隐藏。

class Unit1 :public ThreadBase
{
public:

    void signal1(const std::string& txt)
    {
        std::vector<any> pl;
        pl.push_back(txt);
        std::vector<EmelemtBase*>::const_iterator iter = ElementManager.begin();
        for (; iter != ElementManager.end(); ++iter)
        {
            if ((*iter)->GetId() == 1)
            {
                (*iter)->HitTarget(pl);
            }
        }
    }
    void run()
    {
        signal1("hello world");
    }
};
//=================================================================================


8. 槽类,为了更容易理解宏的作用,在这里给出了我的实验时的代码,如果用注释掉的代码,就不用写MySLOT宏了,由于没有向Qt预处理器一样的自已的预处理,所以在这里采用手动注册的方式来注册槽,也就是说如果你想让一个函数当作槽函数来使用,那么你就要手动写明类似`MySLOT(slot1,std::string)`的语句

class Unit2 :public ThreadBase
{
public:
    //----------- 隐藏函数原型测试---------------
//     void slot1Hide(std::vector<any>& pl)
//     {
//         slot1(any_cast<std::string>(pl.at(0)));
//     }
    //----------基于隐藏函数的宏测试(单参数)-------------
    MySLOT (slot1,std::string)

    void slot1(const std::string& txt)
    {
        std::cout << "hited\t"<<txt.c_str()<< std::endl;
    }
};


9. 最后给一个面函数

int main()
{
    Unit1 obj1;
    Unit2 obj2;

    CONNECT(&obj1, &Unit1::signal1, &obj2, &Unit2::slot1, 1);
    obj1.run();
    system("pause");
    return 0;
}

版本3——含参传递,版本2优化

功能改进包括:
    1. 添加了signal注册
    2. 优化了slot处理

1. 首先是头文件

#include <vector>
#include <iostream>
#include <stdarg.h>
//----------------------
#include "MyAny.h"
//-----------------------


2. 连接基类。同前

class EmelemtBase
{
public:
    virtual void* HitTarget(std::vector<any>& pl) = 0;
    unsigned int GetId() { return id; }

    unsigned int id;
};


3. 记录类型

template<typename C1, typename T1, typename C2, typename T2>
class Element :public EmelemtBase
{
public:
    virtual void* HitTarget(std::vector<any>& pl)
    {
        (targetBody->*targetPtr)(pl);
        //        return const_cast<void*>(targetBody->*targetPtr);
        return nullptr;
    }

    virtual void SetTarget(T1 s, C2 tar, T2 p, const unsigned int& i)
    {
        sourcePtr = s;
        targetBody = tar;
        targetPtr = p;
        id = i;
    }

    C1 sourceBody;
    T1 sourcePtr;
    C2 targetBody;
    T2 targetPtr;
};
//=================================================================================

4. 每个信号可以绑定多个槽,所以为了提升效率,当触发一个信号的时候,只会触发和该信号绑定的若干个槽,所以在这里每一个对象持有一张二维表单,每一行头是信号名,这一行后面记录了属于该信号的槽

class Sender
{
public:
    std::string GetName() { return SenderName; }
    void HitTarget(std::vector<any>& pl)
    {
        Parameters = pl;
        HitTarget();
    }
    void HitTarget(void)
    {
        std::vector<EmelemtBase*>::const_iterator iter = BoxManager.begin();
        for (; iter != BoxManager.end(); ++iter)
        {
            (*iter)->HitTarget(Parameters);
        }
    }
    std::string SenderName;
    std::vector<any> Parameters;
    std::vector<EmelemtBase*> BoxManager;
};


5. 对象基类,这里要对Manager的类型进行修改,修改为Sender类型

class ThreadBase
{
public:
    ThreadBase() {}
    virtual ~ThreadBase()
    {
        std::vector<EmelemtBase*>::iterator iter = BoxManager.begin();
        for (; iter != BoxManager.end(); ++iter)
        {
            if ((*iter) != nullptr)
            {
                delete (*iter);
                (*iter) = nullptr;
            }
        }
    }

    Sender* GetSender(const std::string& name)
    {
        std::vector<Sender*>::iterator iter = SenderManager.begin();
        for (;iter!= SenderManager.end();++iter)
        {
            if (name == (*iter)->GetName())
            {
                return (*iter);
            }
        }
        Sender* tempSender = new Sender();
        tempSender->SenderName = name;
        SenderManager.push_back(tempSender);
        return tempSender;
    }

    std::vector<Sender*> SenderManager;
};


6. 连接函数,这里对接口做了修改,不再需要指定连接序号,但这样触发顺序就不能该类,后期可根据需要来修改,要注意CONNECT宏需要做对应的修改

template<typename C1, typename T1, typename C2, typename T2>
void myconnect(C1 obj1, T1 signal, C2 obj2, T2 slot, unsigned int id,const std::string& name)
{
    Element<C1, T1, C2, T2>* tempElement = new Element<C1, T1, C2, T2>();
    EmelemtBase* tempBox = tempElement;
    tempElement->SetTarget(signal, obj2, slot, id);
    obj1->GetSender(name)->BoxManager.push_back(tempBox);
}
//=================================================================================
#define CONNECT(obj1,signal, obj2, slot, id) \
            myconnect(obj1,signal , obj2, slot##Hide, id,#signal);


7. 名义信号发射宏

#define Emit(signalName, pa) signalName##Hide (std::string(#signalName ),pa);


8. 注册信号的宏,在这里对信号的处理方法进行封装,所以不再需要手动书写实现方法,只需要声明相关的宏就可以了,这里只是实现一个参数的传递。

#define RegistSignal(signalName, paType) void signalName##Hide (std::string& signame, paType pa) \
{ \
    std::vector<any> pl; \
    pl.push_back(pa); \
    std::vector<Sender*>::const_iterator iter = SenderManager.begin(); \
    for (; iter != SenderManager.end(); ++iter) \
    { \
        std::string t = (*iter)->GetName();\
        int a = t.find(signame);\
        if(a != std::string::npos)\
        { \
            (*iter)->HitTarget(pl); \
        } \
    } \
}\
void signalName(paType pa){}
//===============================================================
//半成品,为了实现可变参数传递
//#define MySIGNAL(FunName,ParameterNum, ...) \
//inline void FunName##Hide(const std::vector<any>& pl)\
//{\
//    va_list pl;\
//    va_start(pl,ParameterNum);\
//    for (int i; i<ParameterNum; ++i)\
//    {\
//        FunName(any_cast<>(pl.at[i])); \
//    }\
//}


9. 注册槽的宏,同前

#define RegistSLOT(FunName,ParameterType) \
inline void FunName##Hide (std::vector<any>& pl)\
{\
    FunName(any_cast<ParameterType>(pl.at(0))); \
}

//半成品,为了实现可变参数写的函数体,函数头存在缺陷,没有传入可变参数,函数体没有可变参数解析
// void EmitSignal(std::string& signame) \
// { \
//     std::vector<any> pl; \
//     pl.push_back(parameter); \
//     std::vector<Sender*>::const_iterator iter = SenderManager.begin(); \
//     for (; iter != SenderManager.end(); ++iter) \
//     { \
//         if ((*iter)->GetName() == "&Unit1::signal1") \
//         { \
//             (*iter)->HitTarget(pl); \
//         } \
//     } \
// }

//=================================================================================


10. 信号类,当使用宏的时候,就不用写具体的实现了,通过宏,一句就完事了,这里只有一个参数哦~(你可以根据想法扩展)

class Unit1 :public ThreadBase
{
public:
  //----------基于隐藏函数的宏测试-------------
    RegistSignal(signal1, const std::string&)
  //----------- 隐藏函数原型测试---------------
//    void signal1(const std::string& txt){}
//    {
//         std::vector<any> pl;
//         pl.push_back(txt);
//         std::vector<Sender*>::const_iterator iter = SenderManager.begin();
//         for (;iter!= SenderManager.end();++iter)
//         {
//             if ((*iter)->GetName() == "&Unit1::signal1")
//             {
//                 (*iter)->HitTarget(pl);
//             }
//         }
//    }
    void run()
    {
        Emit(signal1,"hello world")
    }
};
//=================================================================================


11. 槽类,实现了相关槽之后,通过宏注册槽即可

class Unit2 :public ThreadBase
{
public:
    //----------- 隐藏函数原型测试---------------
    //     void slot1Hide(std::vector<any>& pl)
    //     {
    //         slot1(any_cast<std::string>(pl.at(0)));
    //     }
    //----------基于隐藏函数的宏测试(单参数)-------------
    RegistSLOT(slot1, const std::string)

    void slot1(const std::string& txt)
    {
        std::cout << "hited" << txt.c_str() << std::endl;
    }
};


12. main函数示例

int main()
{
    Unit1 obj1;
    Unit2 obj2;

    CONNECT(&obj1, &Unit1::signal1, &obj2, &Unit2::slot1, 1);
    obj1.run();
    system("pause");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/sinat_34130812/article/details/81870570