谈谈Qt信号与槽

关于Qt信号与槽

Qt信号与槽本质类似观察者模式

观察者模式(Observer Pattern)
定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

但是不能因为Qt信号与槽,误解了观察者模式,观察者模式处理多对多还有些思路需要理清。

Qt本身就是基于C++开发而来,在没有信号与槽的时代,C++对象之间的交互需要通过回调函数来实现。

回调函数
使用某对象时,用指针指向另一个对象的函数,这个函数就是回调函数
回调函数的速度要优于信号与槽。
在某个对象被多个对象关联通信的时候,此时这些被关联的对象需要存储在一个容器中,维护容器会使得代码扩展和优化效率低下。

信号与槽工作机制

信号与槽基于元对象来实现的,一下介绍的是旧写法。

connect(sender, SIGNAL(a()), receiver, SLOT(b()));

通过以上connect连接可知,sender是通知者,receiver是观察者,而中间对象是Qt的元对象,sender对象和receiver对象本身也含有Qt的元对象,其中并含有signals信号函数和slots槽函数,SIGNAL和SLOT都是宏,将a和b函数转字符串处理,存储到对应元对象字符串表中,connect函数通过查找元对象中的字符串表,获取到对应的sigId和sltId,然后保存到一个map中,emit宏定义是空,因此emit sigXX()实际上只是执行sigXX(),sigXX()函数会去查询map中对应的receiver对象和sltId,receiver元对象也存在一个槽函数列表,会通过遍历槽函数列表找到sltId去执行指定的槽函数。

使用信号与槽功能需要开启在类声明前使用Q_OBJECT()宏来开启元对象功能。
使用元对象系统的3个条件:
1.只有QObject派生类才可以使用元对象系统特性。
2.在类声明前使用Q_OBJECT()宏来开启元对象功能。
3.使用Moc工具为每个QObject子对象自动生成必要的代码来实现元对象特性。

信号与槽优缺点

相比回调函数,运行性能会低一些,相比直接调用函数会慢很多。
总结便是:
1.发送者需要定位接受信号的对象。
2.需要整个工作过程所有关联,可能是多对多。
3.编组/解组传递的参数。
4.在多线程中可能存在排队情况。

说了缺点,那么为什么还需要信号与槽,下面说说信号与槽的优点:
1.类型安全:槽函数的参数类型和个数必须和信号的参数类型和个数一一对应
2.降低Qt对象的耦合度:在使用过程中,信号对象不关心哪个对象的槽函数接收它的信号,也不关心它的信号是否被接受到,反之同样道理,使用过程哪怕关联对象被释放掉了,也不担心崩溃问题。
3.一个信号可以对应多个槽函数,一个槽函数也可以对应多个信号。
4.信号与槽新写法支持匿名Lambda表达式,写法更简单。
对比观察者设计模式和回调函数,使用方便。

关于多线程下的信号与槽

这里需要介绍信号与槽connect函数里第五个参数了。
1.Qt::AutoConnection:
默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。

2.Qt::DirectConnection:
槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。

3.Qt::QueuedConnection:
槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。

4.Qt::BlockingQueuedConnection:
槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。

5.Qt::UniqueConnection:
这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。

关于信号与槽新旧写法

1.将signal和slot作为字符串处理

QMetaObject::Connection connect(const QObject *, const char *, const QObject *, const char , Qt::ConnectionType); 
//示例
connect(sender, SIGNAL(a()), receiver, SLOT(b()));

2.用函数地址作为信号或者槽传入,在编译器便可以获到类型检查

QMetaObject::Connection connect(const QObject *, const QMetaMethod &, const QObject *, const QMetaMethod &,Qt::ConnectionType);
//示例
connect(sender, &QPushButton::clicked, this, &QWidget::close);

3.此写法就是第二种写法,可以省去this指针

QMetaObject::Connection connect(const QObject *, const char *,const char *,Qt::ConnectionType) const; 
//示例
connect(sender, &QPushButton::clicked, &QWidget::close);

4.同一个类中成员函数之间关联,当成员函数没有重载时,可以直接使用

QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,const QObject *, PointerToMemberFunction,Qt::ConnectionType);
connect(this,&MainWindow::send,this,&MainWindow::close);

5.槽函数为Functor 类型。这个类型可以接受 static 函数、全局函数以及 Lambda 表达式.

QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,Functor);
//示例
connect(sender, &QPushButton::clicked, this, [=](){
	XXX();
});

关于信号与槽新旧写法区别

之前的旧写法(SIGNAL和SLOT)需要注意槽函数必须写到其元对象slots下,而新写法连接的槽函数可以为任意的成员函数,普通全局函数,静态函数。
旧写法中信号如果有参数,必须写上,不能省去。
旧写法中函数不可以省去()。
新写法需要注意重载问题
举例说明

connect(sender, &QPushButton::clicked, &QTimer::start);

QTimer类中存在两个start函数

start(int)
start()

这时候编译器无法确定使用哪一个,因此需要我们确认使用哪一个
解决方法有以下
1.强制类型转换操作符static_cast
编译器隐式执行的任何类型转换都可以由static_cast来完成
需要用到匿名函数指针

typedef void (QTimer:: *timer)(void);
connect(sender, &QPushButton::clicked, static_cast<timer>(&QTimer::start));

2.QOverload类
该函数返回一个指向重载函数的指针,其中的模板参数是重载函数参数类型的列表

connect(sender, &QPushButton::clicked, QOverload<>::of(&QTimer::start));
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

离歌漠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值