Qt信号槽原理

Qt之信号槽原理

一.概述

所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。(这里提一句,Qt 的信号槽使用了额外的处理来实现 ,并不是 GoF 经典的观察者模式的实现方式。)

信号和槽是Qt特有的信息传输机制,是Qt设计程序的重要基础,它可以让互不干扰的对象建立一种联系 Qt信号槽有如下优点:

1.类型安全。需要关联的信号槽的签名必须是等同的。即信号的参数类型和参数个数同接受该信号的槽的参数类型和参数个数相同。若信号和槽签名不一致,编译器会报错。

2.松散耦合。信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是那个对象的那个信号槽接收它发出的信号,它只需在适当的时间发送适当的信号即可,而不需要关心是否被接受和那个对象接受了。Qt就保证了适当的槽得到了调用,即使关联的对象在运行时被删除。程序也不会奔溃。

3.灵活性。一个信号可以关联多个槽,或多个信号关联同一个槽

二.信号槽的实现

众所周知,C++语言的编译过程为预处理->编译->汇编->链接。

a.驱动程序首先运行C预处理器(cpp),将源程序翻译成一个ASCII码的中间文件main.i,
​
b.驱动程序运行C编译器,将main.i翻译成一个ASCII汇编文件main.s
​
c.驱动程序运行汇编器(as),将main.s翻译成一个可重定位目标文件main.o
​
d.运行链接器,将main.o及其一些必要的系统目标文件组合在一起,创建一个可执行的目标文件

但是在qt中,首先会有一个Moc预处理器对源代码进行处理,经过Moc预处理器处理后的代码才是标准的C++代码,此后就可以执行正常的C++编译流程了。正是因为Moc预处理器,Qt才实现了信号槽的功能,下面我们通过用纯C++代码实现一个简洁的信号槽功能来对信号槽深入了解。

使用C++语言模拟信号槽实现:

首先看一下类图:

 

QObject有一个静态元对象QMetaObject,静态元对象存放着信号槽名称的字符信息以及一个根据信号发送者和信号index调用对应槽的函数;

QObject有一个容器connections,此容器是一个map,key是信号index,value是一个Connection,维护者信号槽的对应关系;

QObject有一个静态方法connect,此方法将信号的index作为key,创建一个value插入到对象维护的连接列表容器中;

  • 程序运行时,connect借助两个字符串,即可将信号与槽的关联建立起来,那么,它是如果做到的呢?C++的经验可以告诉我们:

    • 类中应该保存有信号和槽的字符串信息

    • 字符串和信号槽函数要关联

引入元对象系统

定义信号和槽,为了和普通成员进行区分以使得预处理器可以提取信息,定义几个关键字。

#define slots  
#define signals protected  
#define emit  
  • 通过预处理器,将信息提取取来,放置到一个单独的文件中(比如moc_QObject.cpp):

  • 规则很简单,将信号和槽的名字提取出来,放到字符串中。可以有多个信号或槽,按顺序"sig1/nsig2/n"

    static const char sig_names[] = "sig1/nsing2/n";
    static const char slts_names[] = "slot1/nslot2/n";

利用这些信号槽的信息,建立连接;

定义一个结构体,存放信息

struct QMetaObject
{
    const char * sig_names;
    const char * slts_names;
};

然后将它作为QObject的一个静态对象,这个就是Qt中的元对象。

class QObject
{
    static QMetaObject staticMetaObject;
    ...

利用预处理器生成的moc_Object.cpp:

#include "object.h"
​
static const char sig_names[] = "sig1/n";
static const char slts_names[] = "slot1/n";
QMetaObject QObject::staticMetaObject = {sig_names, slts_names};

建立信号槽连接

利用moc预处理器保存的信息,通过 connect 将信号和槽的对应关系保存到一个 mutlimap中。

struct Connection
{
    Object * receiver;
    int method;
};
​
class QObject
{
public:
...
    static void connect(QObject*, const char*, QObject*, const char*);
...
private:
    std::multimap<int, Connection> connections;

connect函数:

void QObject::connect(QObject* sender, const char* sig, QObject* receiver, const char* slt)
{
    int sig_idx = find_string(sender->meta.sig_names, sig);
    int slt_idx = 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));
    }
}

首先从元对象信息中查找信号和槽的名字是否存在,如果存在,则将信号的索引和接收者的信息存入信号发送者的一个map中。

信号的激活:

在Qt中我们都是使用emit来激活一个信号,这是emit的本来面目。

#define emit

在Qt中我们必须要在类里增加一个Q_OBJECT的宏,发送信号使用emit,定义槽和信号,使用signals,public slots等。其实这些都是一些宏替换,在qobjects.h文件里可以看到这些宏的本来面目。

我们这里使用emit来激活信号。我们在定义信号的时候只写了信号的声明,信号的实现并未给出。而槽函数的实现是开发者给出的。其实信号的实现是Moc编译器帮助我们实现的。

void QObject::sig1()
{
    QMetaObject::active(this, 0);
}

信号的调用工作由QMetaObject类来完成

class QObject;
struct QMetaObject
{
    const char * sig_names;
    const char * slts_names;

    static void active(QObject * sender, int idx);

};

这个函数该怎么写呢:思路很简单

从前面的保存连接的map中,找出与该信号关联的对象和槽 调用该对象这个槽

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

void QMetaObject::active(QObject* 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);
    }
}

槽的调用:

这个最后一个关键问题了,槽函数如何根据一个索引值进行调用。

  • 直接调用槽函数我们都知道了,就一个普通函数

  • 可现在通过索引调用了,那么我们必须定义一个接口函数

class QObject
{
    void metacall(int idx);
...

该函数如何实现呢?这个又回到我们的元对象预处理过程中了,因为在预处理的过程,我们能将槽的索引和槽的调用关联起来。

所以,在预处理生成的文件(moc_QObject.cpp)中,我们很容易生成其定义:

void QObject::qt_static_metacall(int idx)
{
	switch (idx)
	{
	case 0:
	{
		slot1();
		break;
	}
	case 1:
	{
		slot2();
		break;
	}
	};
}

总结:moc通过元对象系统保存了信号和槽的字符信息,然后为每一个信号和槽分配编号。当我们调用connect函数时,把信号槽的对应关系保存在对象的一个map容器中。当发送信号时(也就是调用信号函数时)通过刚才保存在map容器中的信号槽对应关系找到对应的接收对象和槽函数。

完整代码:

#include <string.h>  
#include "QObject.h"  
#include <iostream>

static int find_string(const char * str, const char * substr)
{
	if (strlen(str) < strlen(substr))
		return -1;
	int idx = 0;
	int len = strlen(substr);
	bool start = true;
	const char * pos = str;
	char cEnd = '\n';
	while (*pos) {
		if (start && !strncmp(pos, substr, len) && pos[len] == '\n')
			return idx;
		start = false;
		if (*pos == cEnd) {
			idx++;
			start = true;
		}
		pos++;
	}
	return -1;
}

void QObject::connect(QObject* sender, const char* sig, QObject* receiver, const char* slt)
{
	int sig_idx = find_string(sender->staticMetaObject.sig_names, sig);
	int slt_idx = find_string(receiver->staticMetaObject.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 QObject::slot1()
{
	std::cout << "into slot1" << std::endl;
}

void QObject::slot2()
{
	std::cout << "into slot2" << std::endl;
}
/*
从前面的保存连接的map中,找出与
该信号关联的对象和槽调用该对象这个槽
*/
void QMetaObject::active(QObject* 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->qt_static_metacall(c.method);
	}
}

void QObject::testSignal()
{
	emit sig2();
	emit sig1();
}
#pragma  once

#ifndef Q_OBJECT_H  
#define Q_OBJECT_H
#include <map>  

# define slots  
# define signals protected  
# define emit  

# define Q_OBJECT static QMetaObject staticMetaObject;void qt_static_metacall(int idx);


class QObject;
/*元对象*/
struct QMetaObject
{
	const char * sig_names;
	const char * slts_names;
	/*信号的发送者和信号的索引*/
	static void active(QObject * sender, int idx);
};

struct Connection
{
	QObject * receiver;
	int method;
};

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

class QObject
{
	static QMetaObject staticMetaObject; 
	void qt_static_metacall(int idx);

public:
	static void connect(QObject*, const char*, QObject*, const char*);
	void testSignal();

signals:
	void sig1();
	void sig2();

public slots:
	void slot1();
	void slot2();
	friend struct QMetaObject;

private:
	ConnectionMap connections;
};
#endif  
/*此文件手动生成,其实应该是Moc预编译器生成的*/
#include "QObject.h"  

static const char sig_names[] = "sig1\nsig2\n";

static const char slts_names[] = "slot1\nslot2\n";

QMetaObject QObject::staticMetaObject = { sig_names, slts_names };

void QObject::sig1()
{
	QMetaObject::active(this, 0);
}


void QObject::sig2()
{
	QMetaObject::active(this, 1);
}

void QObject::qt_static_metacall(int idx)
{
	switch (idx)
	{
	case 0:
	{
		slot1();
		break;
	}
	case 1:
	{
		slot2();
		break;
	}
	};
}
#include <iostream>
#include <string>
#include "QObject.h"

int main()
{
	QObject obj1, obj2;

	QObject::connect(&obj1, "sig1", &obj2, "slot1");
	QObject::connect(&obj1, "sig2", &obj2, "slot2");
	obj1.testSignal();

	return 0;;
}

下面是Qt中的宏替换

QObject::connect(countObj1, SIGNAL(valueChanged()), countObj2, SLOT(slotValueChanged()));
QObject::connect(countObj1, "2""valueChanged()", countObj2, "1""slotValueChanged()");
#define Q_OBJECT \
public: \
    static const QMetaObject staticMetaObject; \
    virtual const QMetaObject *metaObject() const; \
    virtual void *qt_metacast(const char *); \
    virtual int qt_metacall(QMetaObject::Call, int, void **);
private: \
    static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
    struct QPrivateSignal {};
struct Q_CORE_EXPORT QArrayData
{
    QtPrivate::RefCount ref;
    int size;
    uint alloc : 31;
    uint capacityReserved : 1;

    qptrdiff offset; // in bytes from beginning of header

}
  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Qt是一款流行的跨平台C++框架,有着强大的功能和丰富的类库。Qt的核心机制包括Qt的元对象系统和信号机制Qt的元对象系统是Qt的一个重要特性,它是Qt实现反射的基础。在C++中,反射能够在运行时获取类的信息,如类名、属性、方法等,并在运行时动态地创建、调用对象。Qt的元对象系统通过为每个QObject派生的子类生成一个元对象,实现了C++的反射机制。元对象系统使得Qt能够在运行时获取QObject派生类的信息,并提供了一系列函数来操作这些对象。 Qt信号机制Qt的核心机制之一,它用于实现对象之间的通信。信号机制基于发布-订阅模式,其中一个对象发送信号,而另一个对象通过连接到这个信号函数来接收信号并进行相应的处理。信号机制具有松耦合的特性,可以实现对象之间的解耦。 在信号机制中,信号是由QObject派生类定义的特殊函数,用于声明某个特定事件发生时要发送的信号函数是QObject派生类中的普通函数,用于接收这个信号,并且执行相应的处理逻辑。信号通过信号连接函数进行连接,这样当信号触发时,与之连接的函数就会被自动调用。 Qt的元对象系统和信号机制Qt强大功能的基石。元对象系统实现了C++的反射机制,允许在运行时获取和操作对象的信息。信号机制使对象之间的通信变得简单和易用,提供了一种灵活而高效的方式来实现对象间的解耦。通过这些核心机制Qt能够帮助开发人员更快速、更简便地开发高质量的跨平台应用程序。 ### 回答2: qt核心机制是指Qt框架的底层机制,主要包括Qt元对象系统和Qt信号原理Qt元对象系统是Qt框架中的一个重要概念,它在C++语言的基础上添加了一套元对象(Meta Object)系统。元对象系统在编译过程中生成了额外的代码,使得我们可以在运行时获得更多的对象信息。通过元对象系统,Qt实现了信号机制、宏(MOC)编译和反射等功能。元对象系统实际上是一种面向对象的编程方式,通过它可以实现Qt特有的功能,如动态属性、动态信号等。 Qt信号原理Qt框架中的一个重要特性,用于对象间的通信。信号是一种异步通信方式,通过信号发送者(Sender)发送信号,接收者(Receiver)通过函数(Slot)响应信号信号是通过元对象系统实现的,编译器会在MOC编译阶段解析信号的声明,并在运行时建立连接关系。这种机制使得Qt程序的耦合性更低,灵活性更高,同时也为多线程编程提供了一种方便的方式。 总的来说,Qt核心机制包括了Qt的元对象系统和信号原理。元对象系统为Qt框架提供了反射、动态属性和动态信号等功能,信号机制实现了对象间的异步通信。这些机制使得Qt框架具有高度的可扩展性、灵活性和跨平台性,为开发者提供了一种便捷、高效的方式来构建应用程序。 ### 回答3: Qt是一种跨平台的应用程序框架,具有丰富的功能和强大的性能。Qt核心机制是指Qt框架的基础机制,包括Qt元对象系统和Qt信号原理Qt元对象系统是Qt框架的核心组成之一,用于实现Qt的一些特殊功能,如信号机制和动态属性。Qt元对象系统通过将所有的类对象都派生自QObject基类,实现了一种反射机制,使得对象之间可以动态地连接和交互。通过使用元对象系统,Qt可以实现面向对象编程的高级特性,如对象间的信号的连接,对象的属性系统以及对象的内省(即动态获取对象的属性和方法信息)等。 Qt信号原理Qt框架实现事件驱动的关键机制信号机制允许不同对象之间进行松散的耦合,通过信号的方式进行通信。信号是一种特殊的成员函数,用于表示某个事件的发生,是一种普通的成员函数,用于响应信号的发出。当一个信号被发出时,Qt框架会自动将信号进行匹配,并调用对应的函数。这种机制使得对象之间的通信更加灵活和高效,可以实现事件的传递和处理,避免了显式的函数调用和回调函数的使用。 综上所述,Qt的核心机制包括Qt元对象系统和Qt信号原理。通过元对象系统,Qt实现了一种反射机制,使得对象之间可以动态地连接和交互;通过信号机制Qt实现了一种松散耦合的事件处理方式,提高了对象之间的通信效率和灵活性。这些机制Qt框架的重要组成部分,为开发者提供了更加强大和易用的工具和功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值