目录
1.信号槽机制的原理
1.1.信号槽简介
信号槽(Signals and Slots)是 Qt 框架中用于对象间通信的核心机制,它为开发者提供了一种灵活、类型安全且高效的方式来实现对象间的交互。
- 信号(Signal):信号是特殊的函数,在对象的内部状态发生特定变化时被发射。它只需要声明,无需手动实现具体代码,当满足特定条件时,Qt 会自动处理信号的发射。例如,当用户点击一个按钮时,按钮对象会发射
clicked()
信号。 - 槽(Slot):槽本质上是普通的 C++ 成员函数,可以像其他函数一样被调用,也能在与之连接的信号被发射时自动触发执行。槽函数可以有参数和返回值,其定义和使用与普通 C++ 函数类似。
信号槽是观察者模式的一种实现,特性如下:
A、一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知;
B、一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候——也可以说是信号发出的时候——被调用的函数;
C、信号与槽的连接,形成一种观察者-被观察者的关系;
D、当事件或者状态发生改变的时候,信号就会被发出;同时,信号发出者有义务调用所有注册的对这个事件(信号)感兴趣的函数(槽)。
信号和槽是多对多的关系。一个信号可以连接多个槽,而一个槽也可以监听多个信号。
信号槽与语言无关,有多种方法可以实现信号槽,不同的实现机制会导致信号槽的差别很大。信号槽术语最初来自 Trolltech 公司的 Qt 库,由于其设计理念的先进性,立刻引起计算机科学界的注意,提出了多种不同的实现。目前,信号槽依然是 Qt 库的核心之一,其他许多库也提供了类似的实现,甚至出现了一些专门提供这一机制的工具库。
信号槽是Qt对象以及其派生类对象之间的一种高效通信接口,是Qt的核心特性,也是Qt区别与其他工具包的重要地方。信号槽完全独立于标准的C/C语言,因此要正确的处理好信号和槽,必须借助于一个成为MOC(Meta Object Compiler)的Qt工具,MOC工具是一个C预处理程序,能为高层次的事件处理自动生成所需要的附加代码。
1.2.不同平台的实现
MFC中的消息机制没有采用C++中的虚函数机制,原因是消息太多,虚函数开销太大。在Qt中也没有采用C++中的虚函数机制,而是采用了信号槽机制,原因与此相同。更深层次的原因上,多态的底层实现机制只有两种,一种是按照名称查表,一种是按照位置查表。两种方式各有利弊,而C++的虚函数机制无条件的采用了后者,导致的问题就是在子类很少重载基类实现的时候开销太大,再加上界面编程中子类众多的情况,基本上C++的虚函数机制效率太低,于是各家库的编写者就只好自谋生路,当然,这其实是C++语言本身的缺陷。
2.Qt信号槽实例解析
2.1.信号槽使用示例
使用简单的实例(以Qt5.12.12为例):
#ifndef OBJECT_H
#define OBJECT_H
#include <QObject>
#include <QString>
#include <QDebug>
class Object : public QObject
{
Q_OBJECT
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
Q_PROPERTY(int score READ score WRITE setScore NOTIFY scoreChanged)
Q_PROPERTY(Level level READ level WRITE setLevel)
Q_CLASSINFO("Author", "Scorpio")
Q_CLASSINFO("Version", "1.0")
public:
enum Level
{
Basic = 1,
Middle,
Advanced,
Master
};
Q_ENUMS(Level)
protected:
QString m_name;
Level m_level;
int m_age;
int m_score;
void setLevel(const int& score)
{
if(score <= 60)
{
m_level = Basic;
}
else if(score < 100)
{
m_level = Middle;
}
else if(score < 150)
{
m_level = Advanced;
}
else
{
m_level = Master;
}
}
public:
explicit Object(QString name, QObject *parent = 0):QObject(parent)
{
m_name = name;
setObjectName(m_name);
connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
}
int age()const
{
return m_age;
}
void setAge(const int& age)
{
m_age = age;
emit ageChanged(m_age);
}
int score()const
{
return m_score;
}
void setScore(const int& score)
{
m_score = score;
setLevel(m_score);
emit scoreChanged(m_score);
}
Level level()const
{
return m_level;
}
void setLevel(const Level& level)
{
m_level = level;
}
signals:
void ageChanged(int age);
void scoreChanged(int score);
public slots:
void onAgeChanged(int age)
{
qDebug() << "age changed:" << age;
}
void onScoreChanged(int score)
{
qDebug() << "score changed:" << score;
}
};
#endif // OBJECT_H
Main函数:
#include <QCoreApplication>
#include "Object.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Object ob("object");
//设置属性age
ob.setProperty("age", QVariant(30));
qDebug() << "age: " << ob.age();
qDebug() << "property age: " << ob.property("age").toInt();
//设置属性score
ob.setProperty("score", QVariant(90));
qDebug() << "score: " << ob.score();
qDebug() << "property score: " << ob.property("score").toInt();
qDebug() << "Level: " << ob.level();
ob.setProperty("level", 4);
qDebug() << "level: " << ob.level();
qDebug() << "Property level: " << ob.property("level").toInt();
//内省intropection,运行时查询对象信息
qDebug() << "object name: " << ob.objectName();
qDebug() << "class name: " << ob.metaObject()->className();
qDebug() << "isWidgetType: " << ob.isWidgetType();
qDebug() << "inherit: " << ob.inherits("QObject");
return a.exec();
}
2.2.SIGNAL与SLOT宏
SIGNAL与SLOT宏定义在/src/corelib/kernel/QObjectdefs.h文件中。
Q_CORE_EXPORT const char *qFlagLocation(const char *method);
#define QTOSTRING_HELPER(s) #s
#define QTOSTRING(s) QTOSTRING_HELPER(s)
#ifndef QT_NO_DEBUG
# define QLOCATION "\0" __FILE__ ":" QTOSTRING(__LINE__)
# ifndef QT_NO_KEYWORDS
# define METHOD(a) qFlagLocation("0"#a QLOCATION)
# endif
# define SLOT(a) qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
#else
# ifndef QT_NO_KEYWORDS
# define METHOD(a) "0"#a
# endif
# define SLOT(a) "1"#a
# define SIGNAL(a) "2"#a
#endif
SIGNAL与SLOT宏会利用预编译器将一些参数转化成字符串,并且在前面添加上编码。
在调试模式中,如果signal的连接出现问题,提示警告信息的时候还会注明对应的文件位置。qFlagLocation 用于定位代码对应的行信息,会将对应代码的地址信息注册到一个有两个入口的表里。
Object.h文件中有关SIGNAL与SLOT宏部分代码如下:
connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
通过对Object.h文件进行预编译,得到Object.i文件。
使用G++进行预编译:
g++ -E Object.h -o Object.i -I/usr/local/Trolltech/Qt-5.12.12/include/QtCore -I/usr/local/Trolltech/Qt-5.12.12/include -I.
Object.i文件中结果如下:
connect(this, qFlagLocation("2""ageChanged(int)" "\0" "Object.h" ":" "54"), this, qFlagLocation("1""onAgeChanged(int)" "\0" "Object.h" ":" "54"));
connect(this, qFlagLocation("2""scoreChanged(int)" "\0" "Object.h" ":" "55"), this, qFlagLocation("1""onScoreChanged(int)" "\0" "Object.h" ":" "55"));
2.3.类的元对象
程序编译时make调用MOC对工程源码进行解析,生成相应类的moc_xxx.cpp文件,
const QMetaObject Object::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_Object,
qt_meta_data_Object, &staticMetaObjectExtraData }
};
静态成员staticMetaObject被填充的值如下:
const QMetaObject *superdata;//元数据代表的类的基类的元数据,被填充为基类的元数据指针&QWidget::staticMetaObject
const char *stringdata;//元数据的签名标记,被填充为qt_meta_stringdata_Widget.data
const uint *data;//元数据的索引数组的指针,被填充为qt_meta_data_Widget
const QMetaObject **extradata;//扩展元数据表的指针,内部被填充为函数指针qt_static_metacall。
staticMetaObjectExtraData初始化如下:
const QMetaObjectExtraData Object::staticMetaObjectExtraData = {
0, qt_static_metacall
};
QMetaObjectExtraData类型的内部成员static_metacall是一个指向Object::qt_static_metacall 的函数指针。
Object的内存布局如下:
Object内存布局已经包含了静态成员staticMetaObject和
staticMetaObjectExtraData成员。
const QMetaObject *Object::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
}
QObject::d_ptr->metaObject仅供动态元对象(QML对象)使用,所以一般而言,虚函数 metaObject() 仅返回类的 staticMetaObject。
2.4.元数据表
Qt程序编译时make会调用MOC工具对源文件进行分析,如果某个类包含了Q_OBJECT宏,MOC会生成对应的moc_xxx.cpp文件。
moc_Object.cpp文件内容中:
Object的元数据如下:
static const uint qt_meta_data_Object[] = {
// content:内容信息
6, // revision MOC生成代码的版本号
0, // classname 类名,在qt_meta_stringdata_Object数组中索引为0
2, 14, // classinfo 类信息,有2个cassinfo定义,
4, 18, // methods 类有4个自定义方法,即信号与槽个数,
3, 38, // properties 属性的位置信息,有3个自定义属性,
1, 50, // enums/sets 枚举的位置信息,有一个自定义枚举,在qt_meta_stringdata_Object数组中索引为50
0, 0, // constructors 构造函数的位置信息
0, // flags
2, // signalCount
// classinfo: key, value //类信息的存储在qt_meta_stringdata_Object数组中,
15, 7, //第一个类信息,key的数组索引为15,即Author,value的数组索引为7,即Scorpio
26, 22, //第二个类信息,key的数组索引为26,即Version,value的数组索引为22,即1.0
// signals: signature, parameters, type, tag, flags
39, 35, 34, 34, 0x05, //第一个自定义信号的签名存储在qt_meta_stringdata_Object数组中,
//索引是39,即ageChanged(int)
61, 55, 34, 34, 0x05, //第二个自定义信号的签名存储在qt_meta_stringdata_Object数组中,
//索引是61,即scoreChanged(int)
// slots: signature, parameters, type, tag, flags
79, 35, 34, 34, 0x0a, //第一个自定义槽函数的签名存储在qt_meta_stringdata_Object数组中,
//索引是79,即onAgeChanged(int)
97, 55, 34, 34, 0x0a, //第二个自定义槽函数的签名存储在qt_meta_stringdata_Object数组中,
//索引是79,即onScoreChanged(int)
// properties: name, type, flags
35, 117, 0x02495103, // 第一个自定义属性的签名存储在qt_meta_stringdata_Object中,索引是35,即age
55, 117, 0x02495103, // 第二个自定义属性的签名存储在qt_meta_stringdata_Object中,索引是55,即score
127, 121, 0x0009510b, // 第三个自定义属性的签名存储在qt_meta_stringdata_Object中,索引是127,即level
// properties: notify_signal_id //属性关联的信号编号
0,
1,
0,
// enums: name, flags, count, data
121, 0x0, 4, 54, //枚举的定义,存储在qt_meta_stringdata_Object中,索引是121,即Level,内含4个枚举常量
// enum data: key, value //枚举数据的键值对
133, uint(Object::Basic), //数组索引是133,即Basic
139, uint(Object::Middle), //数组索引是139,即Middle
146, uint(Object::Advanced), //数组索引是146,即Advanced
155, uint(Object::Master), //数组索引是155,即Master
0 // eod 元数据结束标记
};
内省表是一个 uint 数组,分为五个部分:第一部分content,即内容,分为9行。第一行revision,指MOC生成代码的版本号(Qt4 是6,Qt5则是7)。第二个classname,即类名,该值是一个索引,指向字符串表的某一个位置(本例中就是第0位)。
static const char qt_meta_stringdata_Object[] = {
"Object\0Scorpio\0Author\0""1.0\0Version\0\0"
"age\0ageChanged(int)\0score\0scoreChanged(int)\0"
"onAgeChanged(int)\0onScoreChanged(int)\0"
"int\0Level\0level\0Basic\0Middle\0Advanced\0"
"Master\0"
};
2.5.信号的实现
MOC在生成的moc_xxx.cpp文件中实现了信号,创建了一个指向参数的指针的数组,并将指针数组传给QMetaObject::activate函数。数组的第一个元素是返回值。本例中值是0,因为返回值是void。传给activate函数的第三个参数是信号的索引(本例中是0)。
// SIGNAL 0,ageChanged信号的实现
void Object::ageChanged(int _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
// SIGNAL 1 scoreChanged信号的实现
void Object::scoreChanged(int _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
2.6.槽函数的调用
利用槽函数在qt_static_metacall 函数的索引位置来调用槽函数:
void Object::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
Q_ASSERT(staticMetaObject.cast(_o));
Object *_t = static_cast<Object *>(_o);
switch (_id) {
case 0: _t->ageChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: _t->scoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 2: _t->onAgeChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 3: _t->onScoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
default: ;
}
}
}
2.7.元对象中的索引
在每一个QMetaObject对象中,槽、信号以及其它的对象可调用函数都会分配一个从0开始的索引。索引是有顺序的,信号在第一位,槽在第二位,最后是其它函数。这个索引在内部被称为相对索引,不包含父对象的索引位。
为了实现包含在继承链中其它函数的索引,在相对索引的基础上添加一个偏移量,得到绝对索引。绝对索引是在公开API中使用的索引,由QMetaObject::indexOf(Signal, Slot, Method) 类似的函数返回。
连接机制使用以信号为索引的向量。但是在向量中,所有的槽也会占有一定空间,通常在一个对象中,槽的数量要比信号多。所以从 Qt 4.6开始,使用的是一种仅包含信号索引的新的内部实现。
2.8.信号与槽的连接
开始连接时,Qt所要做的第一件事是找出所需要的信号和槽的索引。Qt会去查找元对象的字符串表来找出相应的索引。
然后,创建一个 QObjectPrivate::Connection 对象,将其添加到内部的链表中。
由于允许多个槽连接到同一个信号,需要为每一个信号添加一个已连接的槽的列表。每一个连接都必须包含接收对象和槽的索引。在接收对象销毁的时候,相应的连接也能够被自动销毁。所以每一个接收对象都需要知道谁连接到它自己,以便能够清理连接。
QObject对象的私有数据QObjectPrivate如下:
//QObjectData 原型
class Q_CORE_EXPORT QObjectData {
public:
virtual ~QObjectData() = 0;
QObject *q_ptr;
QObject *parent;
QObjectList children;
uint isWidget : 1;
uint blockSig : 1;
uint wasDeleted : 1;
uint isDeletingChildren : 1;
uint sendChildEvents : 1;
uint receiveChildEvents : 1;
uint isWindow : 1; //for QWindow
uint deleteLaterCalled : 1;
uint unused : 24;
int postedEvents;
QDynamicMetaObjectData *metaObject;
QMetaObject *dynamicMetaObject() const;
};
//QObjectPrivate 原型
class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
Q_DECLARE_PUBLIC(QObject)
public:
struct ExtraData
{
ExtraData() {}
#ifndef QT_NO_USERDATA
QVector<QObjectUserData *> userData;
#endif
QList<QByteArray> propertyNames;
QVector<QVariant> propertyValues;
QVector<int> runningTimers;
QList<QPointer<QObject> > eventFilters;
QString objectName;
};
typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **);
struct Connection
{
QObject *sender;
QObject *receiver;
union {
StaticMetaCallFunction callFunction;
QtPrivate::QSlotObjectBase *slotObj;
};
// The next pointer for the singly-linked ConnectionList
Connection *nextConnectionList;
//senders linked list
Connection *next;
Connection **prev;
QAtomicPointer<const int> argumentTypes;
QAtomicInt ref_;
ushort method_offset;
ushort method_relative;
uint signal_index : 27; // In signal range (see QObjectPrivate::signalIndex())
ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
ushort isSlotObject : 1;
ushort ownArgumentTypes : 1;
Connection() : nextConnectionList(nullptr), ref_(2), ownArgumentTypes(true) {
//ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection
}
~Connection();
int method() const { Q_ASSERT(!isSlotObject); return method_offset + method_relative; }
void ref() { ref_.ref(); }
void deref() {
if (!ref_.deref()) {
Q_ASSERT(!receiver);
delete this;
}
}
};
// ConnectionList is a singly-linked list
struct ConnectionList {
ConnectionList() : first(nullptr), last(nullptr) {}
Connection *first;
Connection *last;
};
struct Sender
{
QObject *sender;
int signal;
int ref;
};
QObjectPrivate(int version = QObjectPrivateVersion);
virtual ~QObjectPrivate();
void deleteChildren();
void setParent_helper(QObject *);
void moveToThread_helper();
void setThreadData_helper(QThreadData *currentData, QThreadData *targetData);
void _q_reregisterTimers(void *pointer);
bool isSender(const QObject *receiver, const char *signal) const;
QObjectList receiverList(const char *signal) const;
QObjectList senderList() const;
void addConnection(int signal, Connection *c);
void cleanConnectionLists();
static inline Sender *setCurrentSender(QObject *receiver,
Sender *sender);
static inline void resetCurrentSender(QObject *receiver,
Sender *currentSender,
Sender *previousSender);
static QObjectPrivate *get(QObject *o) {
return o->d_func();
}
static const QObjectPrivate *get(const QObject *o) { return o->d_func(); }
int signalIndex(const char *signalName, const QMetaObject **meta = nullptr) const;
inline bool isSignalConnected(uint signalIdx, bool checkDeclarative = true) const;
inline bool isDeclarativeSignalConnected(uint signalIdx) const;
// To allow abitrary objects to call connectNotify()/disconnectNotify() without making
// the API public in QObject. This is used by QQmlNotifierEndpoint.
inline void connectNotify(const QMetaMethod &signal);
inline void disconnectNotify(const QMetaMethod &signal);
template <typename Func1, typename Func2>
static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
const typename QtPrivate::FunctionPointer<Func2>::Object *receiverPrivate, Func2 slot,
Qt::ConnectionType type = Qt::AutoConnection);
template <typename Func1, typename Func2>
static inline bool disconnect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
const typename QtPrivate::FunctionPointer<Func2>::Object *receiverPrivate, Func2 slot);
static QMetaObject::Connection connectImpl(const QObject *sender, int signal_index,
const QObject *receiver, void **slot,
QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
const int *types, const QMetaObject *senderMetaObject);
static QMetaObject::Connection connect(const QObject *sender, int signal_index, QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type);
static bool disconnect(const QObject *sender, int signal_index, void **slot);
public:
ExtraData *extraData; // extra data set by the user
QThreadData *threadData; // id of the thread that owns the object
QObjectConnectionListVector *connectionLists; //连接链表向量容器
Connection *senders; // linked list of connections connected to this object
Sender *currentSender; // object currently activating the object
mutable QAtomicInteger<quint32> connectedSignals[2];
union {
QObject *currentChildBeingDeleted; // should only be used when QObjectData::isDeletingChildren is set
QAbstractDeclarativeData *declarativeData; //extra data used by the declarative module
};
// these objects are all used to indicate that a QObject was deleted
// plus QPointer, which keeps a separate list
QAtomicPointer<QtSharedPointer::ExternalRefCountData> sharedRefcount;
};
每一个QObject对象都有一个连接链表容器QObjectConnectionListVector *connectionLists:将每一个信号与一个 QObjectPrivate::Connection 的链表关联起来。
QObject::connect函数的实现如下:
static QMetaObject::Connection connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);
static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,
const QObject *receiver, const QMetaMethod &method,
Qt::ConnectionType type = Qt::AutoConnection);
inline QMetaObject::Connection connect(const QObject *sender, const char *signal,
const char *member, Qt::ConnectionType type = Qt::AutoConnection) const;
#ifdef Q_CLANG_QDOC
template<typename PointerToMemberFunction>
static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection);
template<typename PointerToMemberFunction, typename Functor>
static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor);
template<typename PointerToMemberFunction, typename Functor>
static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection);
#else
//Connect a signal to a pointer to qobject member function
template <typename Func1, typename Func2>
static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
Qt::ConnectionType type = Qt::AutoConnection)
{
typedef QtPrivate::FunctionPointer<Func1> SignalType;
typedef QtPrivate::FunctionPointer<Func2> SlotType;
Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro<typename SignalType::Object>::Value,
"No Q_OBJECT in the class with the signal");
//compilation error if the arguments does not match.
Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount),
"The slot requires more arguments than the signal provides.");
Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<typename SignalType::Arguments, typename SlotType::Arguments>::value),
"Signal and slot arguments are not compatible.");
Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType, typename SignalType::ReturnType>::value),
"Return type of the slot is not compatible with the return type of the signal.");
const int *types = nullptr;
if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
types = QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types();
return connectImpl(sender, reinterpret_cast<void **>(&signal),
receiver, reinterpret_cast<void **>(&slot),
new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
typename SignalType::ReturnType>(slot),
type, types, &SignalType::Object::staticMetaObject);
}
//connect to a function pointer (not a member)
template <typename Func1, typename Func2>
static inline typename std::enable_if<int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0, QMetaObject::Connection>::type
connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot)
{
return connect(sender, signal, sender, slot, Qt::DirectConnection);
}
//connect to a function pointer (not a member)
template <typename Func1, typename Func2>
static inline typename std::enable_if<int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0 &&
!QtPrivate::FunctionPointer<Func2>::IsPointerToMemberFunction, QMetaObject::Connection>::type
connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const QObject *context, Func2 slot,
Qt::ConnectionType type = Qt::AutoConnection)
{
typedef QtPrivate::FunctionPointer<Func1> SignalType;
typedef QtPrivate::FunctionPointer<Func2> SlotType;
Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro<typename SignalType::Object>::Value,
"No Q_OBJECT in the class with the signal");
//compilation error if the arguments does not match.
Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount),
"The slot requires more arguments than the signal provides.");
Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<typename SignalType::Arguments, typename SlotType::Arguments>::value),
"Signal and slot arguments are not compatible.");
Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType, typename SignalType::ReturnType>::value),
"Return type of the slot is not compatible with the return type of the signal.");
const int *types = nullptr;
if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
types = QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types();
return connectImpl(sender, reinterpret_cast<void **>(&signal), context, nullptr,
new QtPrivate::QStaticSlotObject<Func2,
typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
typename SignalType::ReturnType>(slot),
type, types, &SignalType::Object::staticMetaObject);
}
//connect to a functor
template <typename Func1, typename Func2>
static inline typename std::enable_if<QtPrivate::FunctionPointer<Func2>::ArgumentCount == -1, QMetaObject::Connection>::type
connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot)
{
return connect(sender, signal, sender, std::move(slot), Qt::DirectConnection);
}
//connect to a functor, with a "context" object defining in which event loop is going to be executed
template <typename Func1, typename Func2>
static inline typename std::enable_if<QtPrivate::FunctionPointer<Func2>::ArgumentCount == -1, QMetaObject::Connection>::type
connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const QObject *context, Func2 slot,
Qt::ConnectionType type = Qt::AutoConnection)
{
typedef QtPrivate::FunctionPointer<Func1> SignalType;
const int FunctorArgumentCount = QtPrivate::ComputeFunctorArgumentCount<Func2 , typename SignalType::Arguments>::Value;
Q_STATIC_ASSERT_X((FunctorArgumentCount >= 0),
"Signal and slot arguments are not compatible.");
const int SlotArgumentCount = (FunctorArgumentCount >= 0) ? FunctorArgumentCount : 0;
typedef typename QtPrivate::FunctorReturnType<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotArgumentCount>::Value>::Value SlotReturnType;
Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<SlotReturnType, typename SignalType::ReturnType>::value),
"Return type of the slot is not compatible with the return type of the signal.");
Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro<typename SignalType::Object>::Value,
"No Q_OBJECT in the class with the signal");
const int *types = nullptr;
if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
types = QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types();
return connectImpl(sender, reinterpret_cast<void **>(&signal), context, nullptr,
new QtPrivate::QFunctorSlotObject<Func2, SlotArgumentCount,
typename QtPrivate::List_Left<typename SignalType::Arguments, SlotArgumentCount>::Value,
typename SignalType::ReturnType>(std::move(slot)),
type, types, &SignalType::Object::staticMetaObject);
}
#endif //Q_CLANG_QDOC
从Qt5版本开始,槽函数支持了全局函数、类成员函数、lambda表达式等多种样式的可调用对象。
QObject::connect调用QObject::connectImpl检测各种参数的合法性:
QMetaObject::Connection QObject::connectImpl(const QObject *sender, void **signal,
const QObject *receiver, void **slot,
QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
const int *types, const QMetaObject *senderMetaObject)
{
if (!signal) {
qWarning("QObject::connect: invalid null parameter");
if (slotObj)
slotObj->destroyIfLastRef();
return QMetaObject::Connection();
}
int signal_index = -1;
void *args[] = { &signal_index, signal };
for (; senderMetaObject && signal_index < 0; senderMetaObject = senderMetaObject->superClass()) {
senderMetaObject->static_metacall(QMetaObject::IndexOfMethod, 0, args);
if (signal_index >= 0 && signal_index < QMetaObjectPrivate::get(senderMetaObject)->signalCount)
break;
}
if (!senderMetaObject) {
qWarning("QObject::connect: signal not found in %s", sender->metaObject()->className());
slotObj->destroyIfLastRef();
return QMetaObject::Connection(0);
}
signal_index += QMetaObjectPrivate::signalOffset(senderMetaObject);
return QObjectPrivate::connectImpl(sender, signal_index, receiver, slot, slotObj, type, types, senderMetaObject);
}
QObject::connect函数的主要功能是在接受者对象的元对象中将槽函数的相对索引找到,在接受者对象的元对象中将槽函数的相对索引找到,最后调用QMetaObjectPrivate::connect将信号与槽进行连接。QObject及其派生类对象的元对象在创建时就有一个QObjectConnectionListVector连接链表容器,QObject::connect的作用就是将新的连接加入到信号发送者附属的元对象的连接链表容器的相应信号的连接链表中(一个信号可能连接多个槽函数)。
每个QObject及其派生类对象都有一个QObjectConnectionListVector *connectionLists连接链表容器,将信号的索引作为容器的索引,将每一个信号与一个 QObjectPrivate::ConnectionList链表关联起来。同时,QObjectPrivate::ConnectionList链表中连接的某个槽函数可能是接收者对象的槽函数链表中的一个。每个接收者对象的链表如下:
senderList 的 prev 指针是一个指针的指针。这是因为并不是真的指向上一个节点,而是指向上一个节点中的 next 指针。这个指针仅在连接销毁时使用,并且不能向后遍历。它允许不为第一个元素添加特殊处理。
容器中存储的ConnectionList如下:
// ConnectionList is a singly-linked list
struct ConnectionList {
ConnectionList() : first(nullptr), last(nullptr) {}
Connection *first;
Connection *last;
};
每个ConnectionList类型元素是一个双向链表,保存了信号的所有连接。连接的类型Connection结构如下:
typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **);
struct Connection
{
QObject *sender;
QObject *receiver;
union {
StaticMetaCallFunction callFunction;
QtPrivate::QSlotObjectBase *slotObj;
};
// The next pointer for the singly-linked ConnectionList
Connection *nextConnectionList;
//senders linked list
Connection *next;
Connection **prev;
QAtomicPointer<const int> argumentTypes;
QAtomicInt ref_;
ushort method_offset;
ushort method_relative;
uint signal_index : 27; // In signal range (see QObjectPrivate::signalIndex())
ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
ushort isSlotObject : 1;
ushort ownArgumentTypes : 1;
Connection() : nextConnectionList(nullptr), ref_(2), ownArgumentTypes(true) {
//ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection
}
~Connection();
int method() const { Q_ASSERT(!isSlotObject); return method_offset + method_relative; }
void ref() { ref_.ref(); }
void deref() {
if (!ref_.deref()) {
Q_ASSERT(!receiver);
delete this;
}
}
};
每个QObject对象都有一个QObjectConnectionListVector *connectionLists;QObjectConnectionListVector的定义如下:
class QObjectConnectionListVector : public QVector<QObjectPrivate::ConnectionList>
{
public:
bool orphaned; //the QObject owner of this vector has been destroyed while the vector was inUse
bool dirty; //some Connection have been disconnected (their receiver is 0) but not removed from the list yet
int inUse; //number of functions that are currently accessing this object or its connections
QObjectPrivate::ConnectionList allsignals;
QObjectConnectionListVector()
: QVector<QObjectPrivate::ConnectionList>(), orphaned(false), dirty(false), inUse(0)
{ }
QObjectPrivate::ConnectionList &operator[](int at)
{
if (at < 0)
return allsignals;
return QVector<QObjectPrivate::ConnectionList>::operator[](at);
}
};
QMetaObjectPrivate::connectImpl函数源码如下:
//将一个新的连接加入到信号发送者的连接链表容器中相应信号的连接链表中,其中连接加入的连接链表的索引为信号的索引,最终实现信号槽连接的功能:
QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int signal_index,
const QObject *receiver, void **slot,
QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
const int *types, const QMetaObject *senderMetaObject)
{
if (!sender || !receiver || !slotObj || !senderMetaObject) {
const char *senderString = sender ? sender->metaObject()->className()
: senderMetaObject ? senderMetaObject->className()
: "Unknown";
const char *receiverString = receiver ? receiver->metaObject()->className()
: "Unknown";
qWarning("QObject::connect(%s, %s): invalid null parameter", senderString, receiverString);
if (slotObj)
slotObj->destroyIfLastRef();
return QMetaObject::Connection();
}
QObject *s = const_cast<QObject *>(sender);
QObject *r = const_cast<QObject *>(receiver);
QOrderedMutexLocker locker(signalSlotLock(sender),
signalSlotLock(receiver));
if (type & Qt::UniqueConnection && slot) {
QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;
if (connectionLists && connectionLists->count() > signal_index) {
const QObjectPrivate::Connection *c2 =
(*connectionLists)[signal_index].first;
while (c2) {
if (c2->receiver == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) {
slotObj->destroyIfLastRef();
return QMetaObject::Connection();
}
c2 = c2->nextConnectionList;
}
}
type = static_cast<Qt::ConnectionType>(type ^ Qt::UniqueConnection);
}
QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
c->sender = s;
c->signal_index = signal_index;
c->receiver = r;
c->slotObj = slotObj;
c->connectionType = type;
c->isSlotObject = true;
if (types) {
c->argumentTypes.store(types);
c->ownArgumentTypes = false;
}
QObjectPrivate::get(s)->addConnection(signal_index, c.data());
QMetaObject::Connection ret(c.take());
locker.unlock();
QMetaMethod method = QMetaObjectPrivate::signal(senderMetaObject, signal_index);
Q_ASSERT(method.isValid());
s->connectNotify(method);
return ret;
}
最后通过addConnection加入到信号的队列中:
void QObjectPrivate::addConnection(int signal, Connection *c)
{
Q_ASSERT(c->sender == q_ptr);
if (!connectionLists)
connectionLists = new QObjectConnectionListVector();
if (signal >= connectionLists->count())
connectionLists->resize(signal + 1);
ConnectionList &connectionList = (*connectionLists)[signal];
if (connectionList.last) {
connectionList.last->nextConnectionList = c;
} else {
connectionList.first = c;
}
connectionList.last = c;
cleanConnectionLists();
c->prev = &(QObjectPrivate::get(c->receiver)->senders);
c->next = *c->prev;
*c->prev = c;
if (c->next)
c->next->prev = &c->next;
if (signal < 0) {
connectedSignals[0].store(~0);
connectedSignals[1].store(~0);
} else if (signal < (int)sizeof(connectedSignals) * 8) {
connectedSignals[signal >> 5].store(connectedSignals[signal >> 5].load() | (1 << (signal & 0x1f)));
}
}
2.9.信号的发射
使用emit发射信号时,实际调用MOC实现的信号函数,信号函数内部调用了QMetaObject::activate()函数。
// SIGNAL 0,ageChanged信号的实现
void Object::ageChanged(int _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
// SIGNAL 1 scoreChanged信号的实现
void Object::scoreChanged(int _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
{
int signal_index = signalOffset + local_signal_index;
if (sender->d_func()->blockSig)
return;
Q_TRACE_SCOPE(QMetaObject_activate, sender, signal_index);
if (sender->d_func()->isDeclarativeSignalConnected(signal_index)
&& QAbstractDeclarativeData::signalEmitted) {
Q_TRACE_SCOPE(QMetaObject_activate_declarative_signal, sender, signal_index);
QAbstractDeclarativeData::signalEmitted(sender->d_func()->declarativeData, sender,
signal_index, argv);
}
if (!sender->d_func()->isSignalConnected(signal_index, /*checkDeclarative =*/ false)
&& !qt_signal_spy_callback_set.signal_begin_callback
&& !qt_signal_spy_callback_set.signal_end_callback) {
// The possible declarative connection is done, and nothing else is connected, so:
return;
}
void *empty_argv[] = { 0 };
if (qt_signal_spy_callback_set.signal_begin_callback != 0) {
qt_signal_spy_callback_set.signal_begin_callback(sender, signal_index,
argv ? argv : empty_argv);
}
{
QMutexLocker locker(signalSlotLock(sender));
struct ConnectionListsRef {
QObjectConnectionListVector *connectionLists;
ConnectionListsRef(QObjectConnectionListVector *connectionLists) : connectionLists(connectionLists)
{
if (connectionLists)
++connectionLists->inUse;
}
~ConnectionListsRef()
{
if (!connectionLists)
return;
--connectionLists->inUse;
Q_ASSERT(connectionLists->inUse >= 0);
if (connectionLists->orphaned) {
if (!connectionLists->inUse)
delete connectionLists;
}
}
QObjectConnectionListVector *operator->() const { return connectionLists; }
};
ConnectionListsRef connectionLists = sender->d_func()->connectionLists;
if (!connectionLists.connectionLists) {
locker.unlock();
if (qt_signal_spy_callback_set.signal_end_callback != 0)
qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);
return;
}
const QObjectPrivate::ConnectionList *list;
if (signal_index < connectionLists->count())
list = &connectionLists->at(signal_index);
else
list = &connectionLists->allsignals;
Qt::HANDLE currentThreadId = QThread::currentThreadId();
do {
QObjectPrivate::Connection *c = list->first;
if (!c) continue;
// We need to check against last here to ensure that signals added
// during the signal emission are not emitted in this emission.
QObjectPrivate::Connection *last = list->last;
do {
if (!c->receiver)
continue;
QObject * const receiver = c->receiver;
const bool receiverInSameThread = currentThreadId == receiver->d_func()->threadData->threadId.load();
// determine if this connection should be sent immediately or
// put into the event queue
if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
|| (c->connectionType == Qt::QueuedConnection)) {
queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker);
continue;
#if QT_CONFIG(thread)
} else if (c->connectionType == Qt::BlockingQueuedConnection) {
if (receiverInSameThread) {
qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: "
"Sender is %s(%p), receiver is %s(%p)",
sender->metaObject()->className(), sender,
receiver->metaObject()->className(), receiver);
}
QSemaphore semaphore;
QMetaCallEvent *ev = c->isSlotObject ?
new QMetaCallEvent(c->slotObj, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore) :
new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore);
QCoreApplication::postEvent(receiver, ev);
locker.unlock();
semaphore.acquire();
locker.relock();
continue;
#endif
}
QConnectionSenderSwitcher sw;
if (receiverInSameThread) {
sw.switchSender(receiver, sender, signal_index);
}
if (c->isSlotObject) {
c->slotObj->ref();
QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);
locker.unlock();
{
Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.data());
obj->call(receiver, argv ? argv : empty_argv);
}
// Make sure the slot object gets destroyed before the mutex is locked again, as the
// destructor of the slot object might also lock a mutex from the signalSlotLock() mutex pool,
// and that would deadlock if the pool happens to return the same mutex.
obj.reset();
locker.relock();
} else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
//we compare the vtable to make sure we are not in the destructor of the object.
const int methodIndex = c->method();
const int method_relative = c->method_relative;
const auto callFunction = c->callFunction;
locker.unlock();
if (qt_signal_spy_callback_set.slot_begin_callback != 0)
qt_signal_spy_callback_set.slot_begin_callback(receiver, methodIndex, argv ? argv : empty_argv);
{
Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, methodIndex);
callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);
}
if (qt_signal_spy_callback_set.slot_end_callback != 0)
qt_signal_spy_callback_set.slot_end_callback(receiver, methodIndex);
locker.relock();
} else {
const int method = c->method_relative + c->method_offset;
locker.unlock();
if (qt_signal_spy_callback_set.slot_begin_callback != 0) {
qt_signal_spy_callback_set.slot_begin_callback(receiver,
method,
argv ? argv : empty_argv);
}
{
Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, method);
metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
}
if (qt_signal_spy_callback_set.slot_end_callback != 0)
qt_signal_spy_callback_set.slot_end_callback(receiver, method);
locker.relock();
}
if (connectionLists->orphaned)
break;
} while (c != last && (c = c->nextConnectionList) != 0);
if (connectionLists->orphaned)
break;
} while (list != &connectionLists->allsignals &&
//start over for all signals;
((list = &connectionLists->allsignals), true));
}
if (qt_signal_spy_callback_set.signal_end_callback != 0)
qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);
}
//metacall函数内部调用了qt_metacall函数。
int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)
{
if (QMetaObject *mo = object->d_ptr->metaObject)
return static_cast<QAbstractDynamicMetaObject*>(mo)->metaCall(cl, idx, argv);
else
return object->qt_metacall(cl, idx, argv);
}
int Object::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod)
{
if (_id < 4)
qt_static_metacall(this, _c, _id, _a);
_id -= 4;
}
#ifndef QT_NO_PROPERTIES
else if (_c == QMetaObject::ReadProperty)
{
void *_v = _a[0];
switch (_id) {
case 0: *reinterpret_cast< int*>(_v) = age(); break;
case 1: *reinterpret_cast< int*>(_v) = score(); break;
case 2: *reinterpret_cast< Level*>(_v) = level(); break;
}
_id -= 3;
}
else if (_c == QMetaObject::WriteProperty)
{
void *_v = _a[0];
switch (_id) {
case 0: setAge(*reinterpret_cast< int*>(_v)); break;
case 1: setScore(*reinterpret_cast< int*>(_v)); break;
case 2: setLevel(*reinterpret_cast< Level*>(_v)); break;
}
_id -= 3;
} else if (_c == QMetaObject::ResetProperty) {
_id -= 3;
} else if (_c == QMetaObject::QueryPropertyDesignable) {
_id -= 3;
} else if (_c == QMetaObject::QueryPropertyScriptable) {
_id -= 3;
} else if (_c == QMetaObject::QueryPropertyStored) {
_id -= 3;
} else if (_c == QMetaObject::QueryPropertyEditable) {
_id -= 3;
} else if (_c == QMetaObject::QueryPropertyUser) {
_id -= 3;
}
#endif // QT_NO_PROPERTIES
return _id;
}
qt_metacall函数内部调用了qt_static_metacall函数。
2.10.槽函数的调用
槽函数最终通过qt_static_metacall函数根据参数调用相应的槽函数。
void Object::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod)
{
Q_ASSERT(staticMetaObject.cast(_o));
Object *_t = static_cast<Object *>(_o);
switch (_id) {
case 0: _t->ageChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: _t->scoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 2: _t->onAgeChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 3: _t->onScoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
default: ;
}
}
}
3.信号槽的标准C++实现
3.1.Qt元对象的模拟实现
Object类的实现:
#ifndef OBJECT_H
#define OBJECT_H
#include<map>
#include <iostream>
#include <cstring>
using namespace std;
//宏定义
#define SLOT(a) #a
#define SIGNAL(a) #a
#define cpp_slots
#define cpp_signals protected
#define cpp_emit
class Object;
//元对象系统,负责搜集信号与槽的名称
struct MetaObject
{
//信号组
const char * signal;
//槽组
const char * slot;
//激活某个信号,idx为信号索引
static void active(Object * sender, int idx);
};
//被连接对象信息
struct Connection
{
Object * receiver;//信号的接收者
int method;//槽函数索引
};
//保存信号索引与连接对象映射
typedef std::multimap<int, Connection> ConnectionMap;
typedef std::multimap<int, Connection>::iterator ConnectionMapIt;
//信号和槽的索引查找函数,返回信号或槽的索引
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;
while (*pos)
{
if (start && !strncmp(pos, substr, len) && pos[len] == '\n')
return idx;
start = false;
if (*pos == '/n')
{
idx++;
start = true;
}
pos++;
}
return -1;
}
class Object
{
static MetaObject meta;//静态元对象声明
void metacall(int idx);//声明元方法调用函数
public:
Object()
{
}
//建立连接
static void cpp_connect(Object* sender, const char* sig, Object* receiver, const char* slt)
{
cout << "connecting a signal to slot..." << endl;
//从元对象数据表中查看信号和槽是否存在
int sig_idx = find_string(sender->meta.signal, sig);
int slt_idx = find_string(receiver->meta.slot, slt);
//如果没有找到信号或者槽
if (sig_idx == -1 || slt_idx == -1)
{
perror("signal or slot not found!");
}
else
{
//创建一个连接,连接内存储接收者和槽函数的索引
Connection c = { receiver, slt_idx };
cout << "add a signal index and an Connection of receiver to sender's Connection map..." << endl;
//将信号的索引和接收者的信息存储到信号发射者的map容器中
sender->connections.insert(std::pair<int, Connection>(sig_idx, c));
cout << "connected success." << endl;
}
}
void emitSignal()//公有测试函数,发送一个信号
{
cout << "emiting a signal..." << endl;
cpp_emit valueChanged();
}
cpp_signals:
void valueChanged();//信号声明
public cpp_slots:
void onValueChanged()//槽函数
{
cout << "Value Changed."<< endl;
}
friend class MetaObject;
private:
ConnectionMap connections;//连接键值对
};
#endif // OBJECT_H
moc_Object.cpp实现:
#include "Object.h"
//信号的名称
static const char signalNames[] = "valueChanged\n";
//槽的名称
static const char slotNames[] = "onValueChanged\n";
//静态元对象的填充
MetaObject Object::meta = { signalNames, slotNames };
//元方法调用函数的实现,根据连接的索引回调槽函数
void Object::metacall(int idx)
{
switch (idx) {
case 0:
onValueChanged();
break;
default:
break;
};
}
//信号的实现
void Object::valueChanged()
{
MetaObject::active(this, 0);
}
//激活信号
void MetaObject::active(Object* 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);//根据索引调用元方法
}
}
3.2.信号槽模拟使用
Main.cpp文件:
#include <iostream>
#include "Object.h"
using namespace std;
int main(int argc, char *argv[])
{
char p[32] = SLOT(Object);
cout << "cur_value: " << p << endl;
Object obj1, obj2;
//连接信号和槽
Object::cpp_connect(&obj1, SLOT(valueChanged), &obj2, SIGNAL(onValueChanged));
//发射一个信号进行测试
obj1.emitSignal();
getchar();
return 0;
}
4.信号槽的开源实现
4.1.sigslot
sigslot是信号槽的一个非常精炼的C++实现,作者是Sarah Thompson,sigslot实现只有一个头文件sigslot.h,跨平台且线程安全。在WebRTC中,sigslot .h是其基础的事件处理框架, 在多个模块的消息通知,响应处理中被使用。
sigslot库官网:http://sigslot.sourceforge.net/
Sigslot使用示例如下:
#include "sigslot.h"
#include <string>
#include <stdio.h>
#include <iostream>
#include <windows.h>
using namespace sigslot;
using namespace std;
class CSender
{
public:
sigslot::signal2<string, int> m_pfnsigDanger;
void Panic()
{
static int nVal = 0;
char szVal[20] = { 0 };
sprintf_s(szVal,20, "help--%d", nVal);
m_pfnsigDanger(szVal, nVal++);
}
};
class CReceiver :public sigslot::has_slots<>
{
public:
void OnDanger(string strMsg, int nVal)
{
//printf("%s ==> %d", strMsg.c_str(), nVal);
cout << strMsg.c_str() << " ==> " << nVal << endl;
}
};
int main()
{
CSender sender;
CReceiver recever;
cout << "create object ok..." << endl;
sender.m_pfnsigDanger.connect(&recever, &CReceiver::OnDanger);
cout << "connect succ!" << endl;
while (1)
{
cout << "in while..." << endl;
sender.Panic();
Sleep(2000);
cout << "end of sleep" << endl;
}
return 0;
}
如果在Qt工程中使用sigslot.h,sigslot.h中的emit函数名会和Qt中的emit宏冲突,修改方法有两个,一是将sigslot.h的emit改成其他名字,二是在.pro文件中添加DEFINES+=QT_NO_EMIT,禁用Qt的emit宏。
4.2.Boost.Signals
Boost.Signals实现了signals/slots模式,信号(signals)被发射,而插槽(slots)接收该信号。
#include <iostream>
#include "boost/signals.hpp"
void firstSlot() {
std::cout << "void firstSlot()";
}
class secondSlot {
public:
void operator()() const {
std::cout <<
"void secondSlot::operator()() const ";
}
};
int main()
{
boost::signal<void ()> sig;
sig.connect(&firstSlot);
sig.connect(secondSlot());
std::cout << "Emitting a signal... ";
sig();
}
插槽函数的执行顺序是随机的,可以使用分组参数来控制调用顺序。
sig.connect(1,&firstSlot);
sig.connect(2,secondSlot());
4.3.Qt信号槽实现与Boost信号槽实现的区别
Boost.Signals | Qt Signals 和 Slots |
发出信号类似于函数调用 | 发出信号类似于函数调用,Qt 提供了一个 emit 关键字来完成这个操作 |
任何能够访问到信号对象的代码都可以发出信号 | 只有信号的拥有者才能发出信号 |
一个信号就是一个对象 | 信号只能是成员函数 |
信号可以是全局的、局部的或者是成员对象 | 信号只能是成员函数 |
槽是任何可被调用的函数或者函数对象 | 槽是经过特别设计的各种可调用对象 |
可以有返回值,返回值可以在多个槽中使用 | 没有返回值 |
同步的 | 同步的或者队列的 |
非线程安全 | 线程安全,可以跨线程使用 |
当且仅当槽是可追踪的时候,槽被销毁时,连接自动断开 | 槽被销毁时,连接都会自动断开(因为所有槽都是可追踪的) |
类型安全(编译器检查) | 类型安全(运行期检查) |
参数列表必须完全一致 | 槽可以忽略信号中多余的参数 |
信号、槽可以是模板 | 信号、槽不能是模板 |
C++ 直接实现 | 通过由 moc 生成的元对象实现(moc 以及元对象系统都是 C++ 直接实现的) |
没有内省机制 | 可以通过内省发现,可以通过元对象调用,连接可以从资源文件中自动推断出 |