Qt信号槽C++实现

概述
使用Qt的人都知道,信号槽机制是Qt框架的核心机制,使用起来也非常方便,只需要把信号的发送者与接收者通过connect关联起来,然后通过发射信号,触发接收者的槽函数,就可以执行相关的操作。
QObject::connect(sender, signal, receiver, slot);
参数:
sender:发出信号的对象
signal:发送对象发出的信号
receiver:接收信号的对象
slot:接收对象在接收到信号之后所需要调用的函数

信号槽的原理
其实信号槽的原理就是观察者模式的一种实现方式,具体通过回调函数来执行对应的槽函数,槽函数的本质是类的成员函数,基参数可以是任意类型,和普通C++成员函数几乎没有区别,它可以是虚函数,可以被重载,可以是公有的(public),保护的(protected),私有的(private),也可以被其他C++成员函数调用。唯一的区别是:槽可以与信号连接在一起,第当和槽连接的信号被发射的时候,就会调用这个槽。既然是通过回调函数来实现的,那么可不可以用纯C++来实现呢?虽然Qt的源码实现connect函数比较复杂,但我们可以纯C++来简单模拟实现他的原理。


代码实现

//MyObject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <map>
#define My_slots
#define My_signals public
#define My_emit

class MyObject;
struct MetaObject
{
    static void active(MyObject *sender, int idx);
};

struct Connection
{
    MyObject *receiver;
    int slotID;
};

typedef std::map<int, Connection> ConnectionMap;


class MyObject
{
public:
    MyObject();
    virtual ~MyObject();
    static void My_connect(MyObject *sender, int signalID, MyObject *receiver, int slotID);

protected:
    virtual void metacall(int slotID) = 0;

private:
    ConnectionMap connections;
    friend class MetaObject;//这里一定要加friend,不然active()里不能使用对象
    static MetaObject meta;
};

#endif // MYOBJECT_H

 

//MyObject.cpp
#include "MyObject.h"
#include <QDebug>

void MetaObject::active(MyObject *sender, int idx)
{
    Connection c = sender->connections[idx];
    c.receiver->metacall(c.slotID);
}

MyObject::MyObject()
{

}

MyObject::~MyObject()
{

}

void MyObject::My_connect(MyObject *sender, int signalID, MyObject *receiver, int slotID)
{
    Connection c = {receiver, slotID};
    sender->connections.insert(std::pair<int, Connection>(signalID, c));
}

两个测试类继承MyObject类

//CTestA.h
#ifndef CTESTA_H
#define CTESTA_H

#include "MyObject.h"

class CTestA: public MyObject
{
public:
    CTestA();
    void metacall(int slotID);

My_signals:
    typedef enum {
        SIGNAL_1,
        SIGNAL_2,
    }SIGNAL_ID;

    void signalTestA1(int signalID);
    void signalTestA2(int signalID);
};

#endif // CTESTA_H

 

//CTestA.cpp
#include "CTestA.h"
#include <QDebug>

CTestA::CTestA()
{

}

void CTestA::metacall(int slotID)
{
    qDebug() << "slotID===========" << slotID;
}

void CTestA::signalTestA1(int signalID)
{
    MetaObject::active(this, signalID);
}

void CTestA::signalTestA2(int signalID)
{
    MetaObject::active(this, signalID);
}

 

//CTestB.h
#ifndef CTESTB_H
#define CTESTB_H

#include "MyObject.h"

class CTestB: public MyObject
{
public:
    CTestB();
    void metacall(int slotID);

public My_slots:
    typedef enum {
        SLOT_1,
        SLOT_2,
    } SLOT_ID;

    void slotTestB();
    void slotTestB2();
};

#endif // CTESTB_H

 

//CTestB.cpp
#include "CTestB.h"
#include <QDebug>

CTestB::CTestB()
{

}

void CTestB::metacall(int slotID)
{
    switch (slotID) {
        case SLOT_1:
            slotTestB();
            break;
        case SLOT_2:
            slotTestB2();
            break;
        default:
            break;
    }
}

void CTestB::slotTestB()
{
    qDebug() << "exec slotTestB==========";
}

void CTestB::slotTestB2()
{
    qDebug() << "exec slotTestB2==========2";
}

 

main.cpp
#include <QCoreApplication>
#include "CTestA.h"
#include "CTestB.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    CTestA testA;
    CTestB testB;
    MyObject::My_connect(&testA, CTestA::SIGNAL_1, &testB, CTestB::SLOT_1);
    MyObject::My_connect(&testA, CTestA::SIGNAL_2, &testB, CTestB::SLOT_2);
    testA.signalTestA1(CTestA::SIGNAL_1);
    testA.signalTestA1(CTestA::SIGNAL_2);
    return a.exec();
}

运行结果:

 

Qt信号槽的特点,
信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,
槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。
这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),
但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。

信号和槽关联有以下几种模式:
一个信号和一个槽关联;
一个信号和多个槽关联;
多个信号和一个槽关联;
一个信号可以和另外一个信号关联。

信号和槽机制的优点
1、类型安全。需要关联的信号和槽的签名必须是等同。
即信号的参数类型和参数个数 同接收该信号的槽的参数类型和参数个数相同。

2、松散耦合。信号和槽机制减弱了Qt对象的耦合度。
激发信号的Qt对象无须知道是哪个对象的哪个槽需要接收它发出的信号,它只需要做的是在适当的时间发送适当的信号就可以了,

而不需要知道也不关心它的信号有没有被接收到,更不需要知道哪个对象的哪个槽接收到了信号。
同样地,对象的槽也不知道是哪些信号关联了自己,而一旦关联信号和槽,Qt就保证了适合的槽得到了调用。
即使关联的对象在运行时被删除。应用程序也不会崩溃。

信号和槽的效率
信号和槽机制增强了对象间通信的灵活性,然而这也损失了一些性能。同回调函数相比,
信号和槽机制运行速度有些慢。通常,通过传递一个信号来调用槽函数将会比直接调用直接调用非虚函数运行速度慢10倍。
原因如下。
1)需要定位接收信号的对象。
2)安全地遍历所有的关联。
3)编组/解组传递的参数。
4)多线程的时候。信号可能需要排队等待。

然而,与创建对象的new操作及删除对象的delete操作相比,信号和槽的运行代价只是他们很少的一部分。
信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的。

 
 
参考:
 

https://blog.csdn.net/xialianggang1314/article/details/83964392
https://blog.csdn.net/NRC_DouNingBo/article/details/5689657
https://blog.csdn.net/dbzhang800/article/details/6376422
https://blog.csdn.net/qq_21334991/article/details/78073269
https://blog.csdn.net/sea_snow/article/details/81206762
https://blog.csdn.net/liukang325/article/details/78151601
https://blog.csdn.net/zmlovelx/article/details/82686123
https://blog.csdn.net/hyongilfmmm/article/details/83015045

 

 
 
 
 
 
 
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值