Qt信号-槽原理剖析--(2)自己实现信号槽

                                                       时间乃是最大的革新家——培根


先了解一下相关宏:


qt为c++增加的相关宏:signals, slots,emit

在qt的预编译过程中,这些宏会被替换。

1)#define signals public  
Qt4中为 protected. Qt5为支持connect函数指针写法, 定义为 public.

2)#define slots

slots被替换成空! 其作用仅仅是给qt的用户进行提示。

3)#define SLOT(a) "1"#a

     #define SIGNAL(a) "2"#a

连接信号槽使用的宏 (Qt4 的写法, 在函数名前添加数字, 用于标识[2]信号, [1]槽函数, [0]普通方法。)

4)#define emit

emit被替换成空, 其作用仅仅是给qt的用户进行提示。

每个继承了 QObject, 并使用了 Q_OBJECT 的类,被moc 预编译后, 会生成 moc_xxx.cpp 文件,(xxx为类的名字) 。在该文件中有该类的内省表, 内省表记录了索引值 与各函数-信号-槽函数之间的对应关系(每个函数、信号、槽都有一个索引)。 在Qt中都是使用索引值来作为参数传递函数的。

每个类中的每个信号, 都维护了一个 ConnectionList, 用于存储连接到其上的目标函数。

在 connect 时, 需要提供 sender, signal func, receiver, call func. (当然还有连接方式, 不过和此处要讲的流程关系不大). connect 函数将 receiver 和 call func 打包成一个 Connection 节点, 并存储在 sender 的 singnal func 对应的 ConnectionList 中.

在 emit signal 时, 调用了QMetaObject::activate(), 该方法会根据传入的 sender 和 singnal func 对应的索引, 找到 ConnectionList. 并回调这些方法。

Qt5新增了允许将普通函数, lambda等连接到信号上。

Tips: 因为 receiver 在析构时, 需要 disconnect 所有已连接的信号. 所以每个 receiver 也应该维护一个列表, 用于保存已连接的 sender 和 signal.


自己实现信号槽的连接
 

用C++来实现自己的元对象系统。参考了网友的https://blog.csdn.net/fuyunzhishang1/article/details/48345381

我的例子并不是严格按照qt中的逻辑:有一个QObject类,然后其他类继承自这个类。这里只是定义了一个类,主要是为了理解下信号和槽是如何连接起来的。

思路比较简易:先将信号和槽的名字收集起来,然后根据用户写的connect函数将配对的信号和槽利用一系列的数据结构对应起来。

在我的例子中:定义了Object类,类中有成员MetaObject meta;该成员的作用是存储Object类的所有信号的名字,槽的名字。

在main.cpp中有:

    obj1.meta.sig_names = "sig1";  //This should be done by moc in QT.

    obj2.meta.slts_names = "slot1";

这个收集名字的工作在qt中是由moc来完成的,这里手工模仿了。例如,信号名sig1,其中的1代表信号的序号,slot1中1代表槽函数的序号。Object::metacall(int idx)函数根据槽函数的序号来进行响应的调用。

运行一遍代码就可以明白:
 

类定义文件 Object.h

#ifndef OBJECT_H
#define OBJECT_H

#include <map>

//宏定义,防止c++编译器不识别:---所谓的信号或槽,都只是普普通通的C++类的成员函数
# define db_slots
# define db_signals protected
# define db_emit

class Object;

struct MetaObject  //定义一个类,来存放信息. 将该类作为Object的静态成员。
{
    const char * sig_names;
    const char * slts_names;
    static void active(Object * sender, int idx);
};
struct Connection
{
    Object * receiver;
    int method;
};

typedef std::multimap<int, Connection> ConnectionMap;
typedef std::multimap<int, Connection>::iterator ConnectionMapIt;

class Object  //对标qt的QObject类
{
public:
    MetaObject meta;
    void metacall(int idx);  //该函数的实现在db_Object.cpp中,根据索引找到槽函数并调用之

    Object();
    virtual ~Object();
    static int find_string(const char * str, const char * substr);

    //通过 db_connect函数  存储信号接受者以及其槽函数的索引 到一个 ConnectionMap(private变量:connections)。!!!!
    // 存储信号接受者以及其槽函数的索引 构成一个Connection类型,作为ConnectionMap的一个键值对中的值,
    //相应的键为 信号函数在信号发射者内部的索引
    static void db_connect(Object*, const char*, Object*, const char*);
    void testSignal();

    // 为了和普通成员进行区别(以使得预处理器可以知道如何提取信息),创造一些"关键字":db_signals, db_slots,对标qt的signals和slots.

db_signals:
    void sig1();  //qt的QObject类里没有这个,这里纯粹是为了测试。

public db_slots:
    void slot1();

    friend class MetaObject;

private:
    ConnectionMap connections;

};

#endif // OBJECT_H

类实现文件 Object.cpp:

#include "Object.h"
#include <stdio.h>
#include <string.h>


void MetaObject::active(Object* sender, int idx)
{
    ConnectionMapIt it;
    std::pair<ConnectionMapIt, ConnectionMapIt> ret;
    ret = sender->connections.equal_range(idx);
    for (it=ret.first; it!=ret.second; ++it) //调用所有的与该信号相链接的 槽函数(这些槽函数可能属于不同对象)
    {
        Connection c = (*it).second;
        c.receiver->metacall(c.method);
    }
}

/////////////////////////////////////

Object::Object()
{

}

Object::~Object()
{
}

int Object::find_string(const char * str, const char * substr)
{
    std::string str_s = str;
    std::string substr_s = substr;
    if(str_s.find(substr_s) >=0)
    {
        std::string temp = substr_s.substr(substr_s.length()-1,1);
        int a = atoi(temp.c_str());

        return a;
    }

}

//首先从元对象信息中查找信号和槽的名字是否存在,如果存在,
//则将信号的索引和接收者的信息存入 信号发送者 的一个map中。如果信号或槽无效,就什么都不用做了。
void Object::db_connect(Object* sender, const char* sig, Object* receiver, const char* slt)
{
    int sig_idx = Object::find_string(sender->meta.sig_names, sig);
    int slt_idx = Object::find_string(receiver->meta.slts_names, slt);
    if (sig_idx == -1 || slt_idx == -1) {
        perror("signal or slot not found!");
    } else {
        Connection c = {receiver, slt_idx};  //存储信号接受者以及其槽函数的索引
        sender->connections.insert(std::pair<int, Connection>(sig_idx, c));
    }
}

void Object::slot1()
{
    printf("hello hhhhhh!");
}

void Object::metacall(int idx)
{
    switch (idx) {
        case 1:
            slot1();
            break;
        default:
            break;
    };
}
void Object::testSignal()
{
    db_emit sig1();
}
void Object::sig1()
{
    MetaObject::active(this, 1);
}

main.cpp

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

int main()
{
    Object obj1, obj2;

    obj1.meta.sig_names = "sig1";  //This should be done by moc in QT.
    obj2.meta.slts_names = "slot1";

    Object::db_connect(&obj1, "sig1", &obj2, "slot1");
    obj1.testSignal();
    return 0;
}

 

posted @ 2019-01-16 21:45  JadeCicada  阅读(567)  评论(0编辑  收藏  举报