在前一篇中,我翻译了一篇关于信号与槽机制详解的文章Qt信号与槽工作机制–译文。在这一篇文章中,我将根据自己的理解从Qt源码中分析该机制。建议在看本文章之前先去看看前面提到的译文或者直接看原文原文How Qt Signals and Slots Work。本文只分析了大概的思路,很多细节并没有深究下去,如有错误,敬请大家指正。
以下所有代码的测试基于Qt5.4.0,MOC版本是67。
同样,我们还是使用官方的例程进行讲解。
Counter.h
#ifndef COUNTER_H
#define COUNTER_H
#include
#include
class Counter : public QObject
{
Q_OBJECT
int m_value;
public:
int value() const {return m_value;}
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
public:
Counter();
~Counter();
};
#endif // COUNTER_H
Counter.c
#include "counter.h"
Counter::Counter()
{
m_value = 0;
}
void Counter::setValue(int value)
{
if(value != m_value)
{
m_value = value;
emit valueChanged(value);
}
}
Counter::~Counter()
{
}
main.c
#include "counter.h"
int main()
{
Counter a, b;
QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
qDebug() << "First :b.value() is " << b.value();
a.setValue(12);
qDebug() << "Second:b.value() is " << b.value();
return 0;
}
我们在Debug目录下找到MOC根据Counter.h产生的C++文件:moc_counter.cpp
/****************************************************************************
** Meta object code from reading C++ file 'counter.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.4.0)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
#include "../../Test/counter.h"
#include
#include
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'counter.h' doesn't include ."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.4.0. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif
QT_BEGIN_MOC_NAMESPACE
struct qt_meta_stringdata_Counter_t {
QByteArrayData data[6];
char stringdata[46];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_Counter_t, stringdata) + ofs \
- idx * sizeof(QByteArrayData)) \
)
static const qt_meta_stringdata_Counter_t qt_meta_stringdata_Counter = {
{
QT_MOC_LITERAL(0, 0, 7), // "Counter"
QT_MOC_LITERAL(1, 8, 12), // "valueChanged"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 8), // "newValue"
QT_MOC_LITERAL(4, 31, 8), // "setValue"
QT_MOC_LITERAL(5, 40, 5) // "value"
},
"Counter\0valueChanged\0\0newValue\0setValue\0"
"value"
};
#undef QT_MOC_LITERAL
static const uint qt_meta_data_Counter[] = {
// content:
7, // revision
0, // classname
0, 0, // classinfo
2, 14, // methods
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
1, // signalCount
// signals: name, argc, parameters, tag, flags
1, 1, 24, 2, 0x06 /* Public */,
// slots: name, argc, parameters, tag, flags
4, 1, 27, 2, 0x0a /* Public */,
// signals: parameters
QMetaType::Void, QMetaType::Int, 3,
// slots: parameters
QMetaType::Void, QMetaType::Int, 5,
0 // eod
};
void Counter::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
Counter *_t = static_cast(_o);
switch (_id) {
case 0: _t->valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: _t->setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
void **func = reinterpret_cast<void **>(_a[1]);
{
typedef void (Counter::*_t)(int );
if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Counter::valueChanged)) {
*result = 0;
}
}
}
}
const QMetaObject Counter::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_Counter.data,
qt_meta_data_Counter, qt_static_metacall, Q_NULLPTR, Q_NULLPTR}
};
const QMetaObject *Counter::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
void *Counter::qt_metacast(const char *_clname)
{
if (!_clname) return Q_NULLPTR;
if (!strcmp(_clname, qt_meta_stringdata_Counter.stringdata))
return static_cast<void*>(const_cast< Counter*>(this));
return QObject::qt_metacast(_clname);
}
int Counter::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 < 2)
qt_static_metacall(this, _c, _id, _a);
_id -= 2;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 2)
*reinterpret_cast<int*>(_a[0]) = -1;
_id -= 2;
}
return _id;
}
// SIGNAL 0
void Counter::valueChanged(int _t1)
{
void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
QT_END_MOC_NAMESPACE
在main()中,我们首先创建两个Counter类型的对象a和b:Counter a, b;
。
之后我们将对象a中信号valueChanged(int)
与对象b中的槽函数setValue(int)
绑定起来:QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
。
最后调用a对象的槽函数:a.setValue(12);
,调用完该函数之后,对象b中的m_value的值由原来的0变为12。很明显,我们的信号与槽连接成功。
void Counter::setValue(int value)
{
if(value != m_value)
{
m_value = value;
emit valueChanged(value);
}
}
在该函数中,首先判断传入的参数是不是与Counter类的属性变量m_value是否不等,之所以做这一步,是因为如果两者相等还是emit信号,那么这个信号与槽的连接将进入死循环,永远无法结束。最后emit一个信号valueChanged(value)
。在qobject.h中定义了emit这个宏:
qobjectdefs.h
# define emit // nothing
也就是说,在setValue()函数中只是纯粹的调用valueChanged()函数。
我们只在Counter.h声明了一个信号函数:
signals:
void valueChanged(int newValue);
但是我们并没有去实现它,那么它是怎么实现的呢?这时候就能体现出MOC的强大之处了。我们前面有提到过:MOC通过Counter.h函数生成代码保存在moc_counter.cpp的文件中。我们在该C++文件中找到了信号函数valueChanged()
的实现代码:
// SIGNAL 0
void Counter::valueChanged(int _t1)
{
void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
reinterpret_cast
运算符是用来处理无关类型之间的转换,它会产生一个新的值,这个值会与原始参数有完全相同的比特位。reinterpret_cast
的意思是将指向整型_t1的指针转换为const void*。
const_cast是用来溢出变量的const或volatile限定符。const_cast
按我的理解是将指向整型_t1的指针转换为void*。我们只要记住,在这里_a[1]的值是一个指向信号函数参数的指针。
之后就调用QMetaObject::activate()
并传入参数。
还是在moc_counter.cpp文件中,我们找到了staticMetaObject
对象的初始化数据,它是QMetaObject
类型。staticMetaObject
包含了Counter类中一些非常重要的信息,需要多加注意。
const QMetaObject Counter::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_Counter.data,
qt_meta_data_Counter, qt_static_metacall, Q_NULLPTR, Q_NULLPTR}
};
const QMetaObject *Counter::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
我们在qobjectdefs.h中找到了QMetaObject
的构造函数
struct { // private data
const QMetaObject *superdata;
const QByteArrayData *stringdata;
const uint *data;
typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
StaticMetacallFunction static_metacall;
const QMetaObject * const *relatedMetaObjects;
void *extradata; //reserved for future use
} d;
因此我们可以得到如下的等式关系:
1、superdata = &QObject::staticMetaObject;
2、stringdata = qt_meta_stringdata_Counter.data;
3、data = qt_meta_data_Counter;
4、static_metacall = qt_static_metacall;
5、relatedMetaObjects = Q_NULLPTR;
6、extradata = Q_NULLPTR;
其中2、3、4点的信息我们将会在后面用到。
接下来我们来看信号函数valueChanged()
中最后调用的QMetaObject::activate(this, &staticMetaObject, 0, _a);
的函数原型。
QMetaObject::activate()
定义在qobject.cpp文件中。
/*!
\internal
*/
void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
void **argv)
{
activate(sender, QMetaObjectPrivate::signalOffset(m), local_signal_index, argv);
}
/*!
\internal
*/
void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
{
// 详细解释请见文章开头提到的文章
}
activate()函数接收4个参数:
第1个参数表示发送信号的对象,这里当然是指对象a了;
第2个参数是指向QMetaObject类型的指针,这里我们传进去的是&staticMetaObject
,该结构体中包含了很多重要的信息,我们在前面已经讲过了。
第3个参数是在这个类中该信号的索引值。因为我们在Counter类中只定义了一个信号ValueChanged(),所以该信号的索引值为0。那么如果我们定义多个信号,信号的索引值与信号定义出现的顺序呈一一对应关系。
第4个参数是接收一个指向指针的指针,这里传递的是指向数组的指针_a,我们前面也说了_a[1]的值是指向传递给信号函数valueChanged()的参数newValue,很明显,传递给newValue的值是12。。
QMetaObject::activate()
的重载函数:void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
与前一个版本只差别在第2个参数,这里第2个参数传递的是信号的偏移量QMetaObjectPrivate::signalOffset(m)
而不是指向QMetaObject对象的指针m。我一直没有找到QMetaObjectPrivate::signalOffset()
的相关定义,在此根据其函数名理解该函数的作用是:从类中获取信号的偏移量。也就是在qt_meta_data_Counter
数组中找到信号的偏移量。无论是调用哪个版本的activate()函数,都要告知其(传递参数给它)以下信息:
1、发送的对象sender;
2、该信号在所有信号中的偏移量signalOffset;
每一个对象中的每一个信号都维护了一个连接的双向列表connectionList(后面会提到),该链表中都有与该信号相互连接的信息,比如说接收对象,接收的槽函数,参数等等信息。只要找到该信号的偏移量,就可以找到其维护的双向列表,也就可以找到槽函数了。
3、该信号在该类中的索引值local_signal_index;
4、该信号携带的参数_a[1]。
我们这里讲了emit一个信号的过程,但是只提到了信号与槽机制的一点点皮毛。为什么emit一个信号后会有一个槽函数响应呢?信号与槽是怎样绑定的?都绑定哪些信息呢?
在main()函数中,我们调用了QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
将信号与槽绑定起来。在分析connect函数之前,我们先来看看宏SIGNAL()
和SLOT()
都是什么鬼。
我们在qobjectdefs.h中找到了那两个宏的定义:
#ifndef QT_NO_META_MACROS
#ifndef QT_NO_DEBUG
# define QLOCATION "\0" __FILE__ ":" QT_STRINGIFY(__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
这两个宏无非是将信号与槽函数的名称(函数名以及参数)转换为字符串,仅此而已。在方法名称前面给个标识0,在槽函数前面给个标识1,在信号函数前面给个标识2,这3个标识为后面解析参数是所属哪种类型的起到很大的作用。我们在这边是不是可以推测这就是很多编程语言的函数名不能以数字开头的原因之一吗?还有我们注意到,这里的宏是将信号与槽的名称转换为字符串,这里的名称包括了参数。于是我们可以得出:信号与槽的参数不能包括宏。如果包括宏,是要等里面的宏先展开然后SLOT和SIGNAL再展开还是先SLOT和SIGNAL展开呢?这都是没有定义的。
好激动,大boss出现了,秒了它。。。
我们在这里使用的是Qt4.8版本之前旧的connect()
语法,如果想要理解Qt5.0以上版本该语法的解释,可以去看这篇文章:Signals and Slots in Qt5.
我们首先看看这个函数介绍(在qobject.cpp文件中):
threadsafe Creates a connection of the given \a type from the \a signal in the \a sender object to the \a method in the \a receiver object. Returns a handle to the connection that can be used to disconnect it later. You must use the \c SIGNAL() and \c SLOT() macros when specifying the \a signal and the \a method, for example: \snippet code/src_corelib_kernel_qobject.cpp 22 This example ensures that the label always displays the current scroll bar value. Note that the signal and slots parameters must not contain any variable names, only the type. E.g. the following would not work and return false: \snippet code/src_corelib_kernel_qobject.cpp 23 A signal can also be connected to another signal: \snippet code/src_corelib_kernel_qobject.cpp 24 In this example, the \c MyWidget constructor relays a signal from a private member variable, and makes it available under a name that relates to \c MyWidget. A signal can be connected to many slots and signals. Many signals can be connected to one slot. If a signal is connected to several slots, the slots are activated in the same order in which the connections were made, when the signal is emitted. The function returns a QMetaObject::Connection that represents a handle to a connection if it successfully connects the signal to the slot. The connection handle will be invalid if it cannot create the connection, for example, if QObject is unable to verify the existence of either \a signal or \a method, or if their signatures aren't compatible. You can check if the handle is valid by casting it to a bool. By default, a signal is emitted for every connection you make; two signals are emitted for duplicate connections. You can break all of these connections with a single disconnect() call. If you pass the Qt::UniqueConnection \a type, the connection will only be made if it is not a duplicate. If there is already a duplicate (exact same signal to the exact same slot on the same objects), the connection will fail and connect will return an invalid QMetaObject::Connection. The optional \a type parameter describes the type of connection to establish. In particular, it determines whether a particular signal is delivered to a slot immediately or queued for delivery at a later time. If the signal is queued, the parameters must be of types that are known to Qt's meta-object system, because Qt needs to copy the arguments to store them in an event behind the scenes. If you try to use a queued connection and get the error message \snippet code/src_corelib_kernel_qobject.cpp 25 call qRegisterMetaType() to register the data type before you establish the connection. \sa disconnect(), sender(), qRegisterMetaType(), Q_DECLARE_METATYPE()
上面的介绍总结起来有以下几点:
1、返回值是一个该连接的句柄,用于删除连接与表示连接成功与否;
2、这个版本的connect()必须要使用SIGNAL() 和 SLOT()宏指定信号和槽;
3、一个信号可以与其他信号连接;
4、一个信号可以与许多槽函数或者信号连接;多个信号可以与一个槽函数连接;
5、当一个信号与多个槽函数连接时,槽函数的响应是按照connect的顺序来的;
6、可选的参数用来指定槽函数是立即响应信号或者是稍后响应。
接下来我们要看到connect()的真面目了,我们在main.c里面调用了
QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
我们把宏SIGNAL()和SLOT()展开,实际上传递的参数是如下的:
QObject::connect(&a, "2valueChanged(int)", &b, "1setValue(int)");
最后,我们来看看connect()的函数实现吧:
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type)
{
// (1)
if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
sender ? sender->metaObject()->className() : "(null)",
(signal && *signal) ? signal+1 : "(null)",
receiver ? receiver->metaObject()->className() : "(null)",
(method && *method) ? method+1 : "(null)");
return QMetaObject::Connection(0);
}
QByteArray tmp_signal_name;
// (2)
if (!check_signal_macro(sender, signal, "connect", "bind"))
return QMetaObject::Connection(0);
const QMetaObject *smeta = sender->metaObject(); // (3)
const char *signal_arg = signal;
++signal; //skip code
QArgumentTypeArray signalTypes;
Q_ASSERT(QMetaObjectPrivate::get(smeta)->revision >= 7);
QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes); // (4)
// (5)
int signal_index = QMetaObjectPrivate::indexOfSignalRelative(
&smeta, signalName, signalTypes.size(), signalTypes.constData());
if (signal_index < 0) {
// check for normalized signatures
tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
signal = tmp_signal_name.constData() + 1;
signalTypes.clear();
signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
smeta = sender->metaObject();
signal_index = QMetaObjectPrivate::indexOfSignalRelative(
&smeta, signalName, signalTypes.size(), signalTypes.constData());
}
if (signal_index < 0) {
err_method_notfound(sender, signal_arg, "connect");
err_info_about_objects("connect", sender, receiver);
return QMetaObject::Connection(0);
}
signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);
signal_index += QMetaObjectPrivate::signalOffset(smeta);
// (6)
QByteArray tmp_method_name;
int membcode = extract_code(method);
if (!check_method_code(membcode, receiver, method, "connect"))
return QMetaObject::Connection(0);
const char *method_arg = method;
++method; // skip code
QByteArray methodName;
QArgumentTypeArray methodTypes;
const QMetaObject *rmeta = receiver->metaObject();
int method_index_relative = -1;
Q_ASSERT(QMetaObjectPrivate::get(rmeta)->revision >= 7);
// (7)
switch (membcode) {
case QSLOT_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
case QSIGNAL_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
}
if (method_index_relative < 0) {
// check for normalized methods
tmp_method_name = QMetaObject::normalizedSignature(method);
method = tmp_method_name.constData();
methodTypes.clear();
methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
// rmeta may have been modified above
rmeta = receiver->metaObject();
switch (membcode) {
case QSLOT_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
case QSIGNAL_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
}
}
if (method_index_relative < 0) {
err_method_notfound(receiver, method_arg, "connect");
err_info_about_objects("connect", sender, receiver);
return QMetaObject::Connection(0);
}
// (8)
if (!QMetaObjectPrivate::checkConnectArgs(signalTypes.size(), signalTypes.constData(),
methodTypes.size(), methodTypes.constData())) {
qWarning("QObject::connect: Incompatible sender/receiver arguments"
"\n %s::%s --> %s::%s",
sender->metaObject()->className(), signal,
receiver->metaObject()->className(), method);
return QMetaObject::Connection(0);
}
// (9)
int *types = 0;
if ((type == Qt::QueuedConnection)
&& !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) {
return QMetaObject::Connection(0);
}
#ifndef QT_NO_DEBUG
QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset());
check_and_warn_compat(smeta, smethod, rmeta, rmethod);
#endif
// (10)
QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));
return handle;
}
我来分别解释一下代码中各段的意思:
(1):参数的有效性检查
判断传递进来的4个参数指针是否都为NULL,若有NULL,提供警告信息并返回错误;
(2):检查信号的有效性
实际上就是判断SIGNAL()
展开后的得到的字符串的前面的标识是否为2。
if (!check_signal_macro(sender, signal, "connect", "bind"))
return QMetaObject::Connection(0);
static bool check_signal_macro(const QObject *sender, const char *signal,
const char *func, const char *op)
{
int sigcode = extract_code(signal);
if (sigcode != QSIGNAL_CODE) {
if (sigcode == QSLOT_CODE)
qWarning("QObject::%s: Attempt to %s non-signal %s::%s",
func, op, sender->metaObject()->className(), signal+1);
else
qWarning("QObject::%s: Use the SIGNAL macro to %s %s::%s",
func, op, sender->metaObject()->className(), signal);
return false;
}
return true;
}
(3):获取得到staticMetaObject
,实际上就是Counter类的详细信息moc_counter.cpp,我们在文章的开头处有讲过了,这里面包含了很多该重要信息。
const QMetaObject *smeta = sender->metaObject();
(4):将信号的名称与参数分开
QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
// Given a method \a signature (e.g. "foo(int,double)"), this function
// populates the argument \a types array and returns the method name.
QByteArray QMetaObjectPrivate::decodeMethodSignature(
const char *signature, QArgumentTypeArray &types)
{
Q_ASSERT(signature != 0);
const char *lparens = strchr(signature, '(');
if (!lparens)
return QByteArray();
const char *rparens = strrchr(lparens + 1, ')');
if (!rparens || *(rparens+1))
return QByteArray();
int nameLength = lparens - signature;
argumentTypesFromString(lparens + 1, rparens, types);
return QByteArray::fromRawData(signature, nameLength);
}
将信号的名称作为返回值给signalName
,将参数信息传递给signalTypes
。把信号的名称与参数分开。
(5):第(5)到第(6)之间的内容看不懂,只知道它是为了获取信号在类中的索引值,为什么要大费周章的获取真是搞不明白。
推测与moc_counter.cpp中的qt_meta_data_Counter
有关。
(6):从(6)开始往下到(8)是处理槽函数相关的工作,它所做的处理与信号函数是一样的。
(7):从(7)的代码中看到switch case 语句不仅检测SLOT
的标识符1并且还检测SIGNAL
的标识符2,是不是信号也可以和信号绑定的呢??我有在代码中进行验证,将信号与信号connect在一起,我在main.c中稍微做了修改:
int main()
{
Counter a, b, c;
QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SIGNAL(valueChanged(int)));
QObject::connect(&b, SIGNAL(valueChanged(int)), &c, SLOT(setValue(int)));
qDebug() << "First :c.value() is " << c.value();
a.setValue(12);
qDebug() << "Second:c.value() is " << c.value();
return 0;
}
很明显,c.value()
的结果是12。所以说:信号与信号是可以绑定的。
(8):检测信号函数和槽函数的参数类型以及参数个数是否是一致的。
(9):判断connect()
第五个可选参数的连接类型。
(10):好吧,这个大boss还有分身。
QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));
好,让我们看看这个分身的原型:
QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,
int signal_index, const QMetaObject *smeta,
const QObject *receiver, int method_index,
const QMetaObject *rmeta, int type, int *types)
它的返回值是一个指向QObjectPrivate::Connection
类型的指针。
第1个参数是发送信号的对象sender;
第2个参数是信号在类中的索引值signal_index;
第3个参数是发送对象的元对象smeta;
第4个参数是响应信号的对象receiver;
第5个参数是响应信号槽函数的索引值method_index_relative;
第6个参数是接收对象的元对象rmeta;
第7个和第8个参数是该连接的类型。
那我们继续看该分身的实现,有些内容将省略。
QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,
int signal_index, const QMetaObject *smeta,
const QObject *receiver, int method_index,
const QMetaObject *rmeta, int type, int *types)
{
// 省略
QObjectPrivate::StaticMetaCallFunction callFunction =
rmeta ? rmeta->d.static_metacall : 0;
//省略
QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
c->sender = s;
c->signal_index = signal_index;
c->receiver = r;
c->method_relative = method_index;
c->method_offset = method_offset;
c->connectionType = type;
c->isSlotObject = false;
c->argumentTypes.store(types);
c->nextConnectionList = 0;
c->callFunction = callFunction;
QObjectPrivate::get(s)->addConnection(signal_index, c.data());
locker.unlock();
QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
if (smethod.isValid())
s->connectNotify(smethod);
return c.take();
}
QObjectPrivate::StaticMetaCallFunction callFunction =
rmeta ? rmeta->d.static_metacall : 0;
这个语句是从接收对象的元对象中获取调用函数(回调函数)的信息static_metacall
。我们在前面得出
static_metacall = qt_static_metacall;
qt_static_metacall
又在moc_counter.cpp文件中,所以说moc_counter.cpp很重要吧。
接下来是创建一个QObjectPrivate::Connection
类型的变量c(这个好像是模板吧,对于我的C++知识表示很着急。。)。反正我只知道把一些相关信息保存在connectionList中。至于怎样保存?如何保存?保存哪些东西?现在都还没有深究过。后面再把这里的知识补上。
1、当我们在xxx.h文件中声明了信号和槽函数相关的内容,MOC会根据该内容生成一个对相应moc_xxx.cpp的文件,里面包含了该.h文件中很多重要的信息,比如信号与槽的索引值;
2、当我们在程序中调用connect()
函数绑定信号与槽,实际上在内部做了很多个工作。为发送对象的信号所维护的connectionLists中添加接收对象的相关信息(名称、槽函数名称、参数类型……),实际上就是信息的绑定。
3、当我们在程序中emit一个信号,会从该信号维护的connectionLists找到与之绑定的槽函数并执行。
4、Qt中信号与槽的机制确实是通过回调函数实现的,但并不仅仅是这样的。它在内部做了很多的努力,比如安全性的保证、比如使用connectionLists保证信号与槽简单方便的使用。