信号槽简介
- 在Qt中对象间通信,广泛使用了信号槽机制。信号槽机制本质上就是回调函数的应用,Qt中通过扩展C++语法来实现,对比回调函数使用更便利,定制性更好。
- C++语言扩展是通过元对象编译器moc来实现,Qt 将源代码交给标准 C++ 编译器,如 gcc,mingw,msvc之前,需要事先将这些扩展的语法去除掉,完成这一操作的就是 moc。
元对象编译器及其作用
- moc 全称是 Meta-Object Compiler,也就是“元对象编译器”。
- Qt程序在交给标准编译器预编译之前要使用 moc 分析 C++ 源文件。
- 如头文件中包含宏 Q_OBJECT,则生成一个包含Q_OBJECT 宏的实现代码的C++源文件。
- 这个新的文件名字将会是原文件名前面加上 moc_ 构成。
- 新生成的源文件将参与到标准编译器的编译中。
- qt程序编译执行过程:moc预编译=》标准编译器预编译=》标准编译器编译=》链接执行
信号槽使用注意
- 对象类必须直接或间接继承QObject。
- 对象类的私有声明区必须声明Q_OBJECT宏。
- signals下必须定义相应的信号函数。
- slots下必须定义并实现相应的槽函数。
- 使用connect进行正确关联。
connect函数第五个参数
- Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型;如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
- Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这种连接类型在多线程环境下比较危险,可能会造成崩溃。
- Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这种连接类型。
- Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在线程间同步的场合下可能需要这种连接类型。
- Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置,且某个信号和槽已经连接时,再进行重复连接的话就会失败。也就是避免了重复连接,即发送同一个信号,槽函数多次执行的情况。
信号槽简易实现
- 依次包含KObject,TestClassA,TestClassB,模拟qt中信号槽实现,详细代码如下:
#ifndef KOBJECT_H
#define KOBJECT_H
#include<map>
#define kslots
#define ksignals public
#define kemit
class KObject;
struct MetaObject
{
static void active(KObject * sender, int idx);
};
struct Connection
{
KObject * receiver;
int sltID;
};
typedef std::map<int, Connection> ConnectionMap;
class KObject
{
friend class MetaObject;
static MetaObject meta;
public:
KObject();
virtual ~KObject();
static void kconnect(KObject*, int, KObject*, int);
protected:
virtual void metacall(int sltID) = 0;
private:
ConnectionMap connections;
};
#endif
#include "KObject.h"
KObject::KObject(){}
KObject::~KObject(){}
void MetaObject::active(KObject* sender, int sigID)
{
Connection c = sender->connections[sigID];
c.receiver->metacall(c.sltID);
}
void KObject::kconnect(KObject* sender, int sigID, KObject* receiver, int sltID)
{
Connection c = {receiver, sltID};
sender->connections.insert(std::pair<int, Connection>(sigID, c));
}
#ifndef TESTCLASSA_H
#define TESTCLASSA_H
#include "KObject.h"
class TestClassA : public KObject
{
public:
TestClassA();
protected:
void metacall(int sltID);
ksignals:
typedef enum {
SIG_HELLOWORLD
}SIG_ID;
void sigTestA(int sigID);
};
#endif
#include "TestClassA.h"
TestClassA::TestClassA()
{
}
void TestClassA::metacall(int sltID)
{
}
void TestClassA::sigTestA(int sigID)
{
MetaObject::active(this, sigID);
}
#ifndef TESTCLASSB_H
#define TESTCLASSB_H
#include "KObject.h"
class TestClassB : public KObject
{
public:
TestClassB();
void metacall(int sltID);
public kslots:
typedef enum{
SLT_HELLOWORLD = 2
}SLT_ID;
void slotTestB();
};
#endif
#include "TestClassB.h"
#include <iostream>
TestClassB::TestClassB()
{
}
void TestClassB::metacall(int sltID)
{
switch (sltID)
{
case SLT_HELLOWORLD:
slotTestB();
break;
default:
break;
};
}
void TestClassB::slotTestB()
{
std::cout << "hello world TestB";
}
#include "TestClassA.h"
#include "TestClassB.h"
int main()
{
TestClassA a;
TestClassB b;
KObject::kconnect(&a, TestClassA::SIG_HELLOWORLD, &b, TestClassB::SLT_HELLOWORLD);
a.sigTestA(TestClassA::SIG_HELLOWORLD);
return 0;
}