1.讲述Qt信号槽机制与优势与不足
优点: ①类型安全。需要关联的信号槽的签名必须是等同的。即信号的参数类型和参数个数同接受该信号的槽的参数类型和参数个数相同。若信号和槽签名不一致,编译器会报错。
②松散耦合。信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是那个对象的那个信号槽接收它发出的信号,它只需在适当的时间发送适当的信号即可,而不需要关心是否被接受和那个对象接受了。Qt就保证了适当的槽得到了调用,即使关联的对象在运行时被删除。程序也不会奔溃。 ③灵活性。一个信号可以关联多个槽,或多个信号关联同一个槽。
不足:速度较慢。与回调函数相比,信号和槽机制运行速度比直接调用非虚函数慢10倍。
原因:①需要定位接收信号的对象。②安全地遍历所有关联槽。③编组、解组传递参数。④多线程的时候,信号需要排队等待。(然而,与创建对象的new操作及删除对象的delete操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的。)
2.Qt信号和槽的本质是什么
回调函数。信号或是传递值,或是传递动作变化;槽函数响应信号或是接收值,或者根据动作变化来做出对应操作。
3.Qt信号槽的调用流程
-
MOC查找头文件中的signal与slots,标记出信号槽。将信号槽信息储存到类静态变量staticMetaObject中,并按照声明的顺序进行存放,建立索引。
-
connect链接,将信号槽的索引信息放到一个双向链表中,彼此配对。
-
emit被调用,调用信号函数,且传递发送信号的对象指针,元对象指针,信号索引,参数列表到active函数。
-
active函数在双向链表中找到所有与信号对应的槽索引,根据槽索引找到槽函数,执行槽函数。
4.信号槽的实现:元对象编译器MOC
元对象编译器MOC负责解析signals、slot、emit等标准C++不存在的关键字,以及处理Q_OBJECT、Q_PROPERTY、Q_INVOKABLE等相关的宏,生成moc_xxx.cpp的C++文件(使用黑魔法来变现语法糖)。比如信号函数只要声明、不需要自己写实现,就是在这个moc_xxx.cpp文件中自动生成的。
moc的本质就是反射器。
5.Qt信号槽的链接方式(connect的第五个参数)
-
Qt::AutoConnection
: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。 -
Qt::DirectConnection
:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。 -
Qt::QueuedConnection
:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。 -
Qt::BlockingQueuedConnection
:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。 -
Qt::UniqueConnection
:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。
6.多个槽函数的执行顺序
和连接方式、线程等多种因素有关。一般在单线程情况下,是按照连接的顺序执行的;多线程情况下无法确定执行顺序,会受到线程调度以及事件循环的影响。
1.直接连接方式:触发信号的线程会直接调用与该信号连接的槽函数,并且所有的槽函数都会在同一个线程中被执行。因此,多个槽函数的执行顺序是由连接的先后顺序决定的。
2.排队连接方式:会将所有槽函数的执行任务放到事件队列中,并等待事件循环执行。因此,多个槽函数的执行顺序是按照连接的先后顺序进行的。先连接的槽函数会先被执行,后连接的槽函数会后被执行。
3.自动连接方式:自动连接方式既可以视为直接连接方式,也可以视为排队连接方式。当信号与槽在同一个线程中时,会自动使用直接连接方式,多个槽函数的执行顺序与直接连接方式一样;当信号与槽在不同的线程中时,会自动使用排队连接方式,多个槽函数的执行顺序与排队连接方式一样。
4.唯一连接方式:会确保一个信号只与一个槽函数连接,并且只有最后的一个槽函数会被执行。因此,在使用唯一连接方式时,只会有一个槽函数被执行。
5.阻塞连接方式:与排队连接方式类似,都是将槽函数插入到事件队列中等待执行。但是它在等待槽函数执行完成之前会阻塞发送信号的线程,因此它只能用于不同线程中的信号与槽的连接。因此,多个槽函数的执行顺序与排队连接方式一样,先连接的槽函数会先被执行,后连接的槽函数会后被执行。
7.Qt内存管理
Qt的内存管理是半自动的内存管理。也就是说有一部分内存能够自动回收,但是还有一部分需要用户手动回收。
主要方式有:对象树、智能指针、手动管理
Qt的半自动内存管理机制主要基于一个概念:父子关系。
父子关系:一个对象可以通过setParent()函数来指定它的父对象。如果一个对象没有父对象,则它是一个顶层对象。父对象和子对象之间的关系通过指针来建立。每个QObject对象都有一个指向其父对象的指针,同时还有一个指针列表来存储其所有的子对象。父对象负责管理其子对象的生命周期,当父对象被销毁时,所有的子对象也会被自动销毁。
对象树是在父子关系的基础上的一个内存管理机制。
对象树:Qt提供了一种自动管理继承自QObject对象的内存资源的机制,即对象树。当创建Qt对象时,在构造函数中可以指定父对象,指定了父对象的Qt对象会在父对象的内存回收时自动被回收。当一个对象的父对象被设置为另一个对象时,它会从原来的对象树中移除,并成为新的对象树的一部分。
Qt还提供了两个重要的内存管理类: QObject 和QPointer。
-
QObject类是Qt中所有对象的基类。它提供了一些内存管理的功能,如自动删除和对象名称管理。QObject 类还提供了一个Q0bject:deleteLater()函数,它会将对象的销毁请求放入事件队列中,等待下一个事件循环周期时再进行实际的销毁操作。这种延迟销毁的机制可以在一些场景下非常有用。
-
QPointer类是一个智能指针,它用于管理QObject对象的指针。它会在指向的对象被销毁时自动将指针置为nullptr,以避免出现悬空指针。