目录
2个类型和1个签名
Qt::ConnectionType
信号的连接类型
- Qt::DirectConnection
直接连接,相当于在emit信号的地方直接调用接收者的槽函数,多见于在同一线程中去连接信号和槽,是一个同步的调用过程。 - Qt::QueuedConnection
队列连接,在emit信号时,会往接收者所在线程的消息队列post一个QMetaCallEvent,接收者线程在未来某个时刻处理消息队列中的该事件时就会调用接收者的槽函数,是一个异步的调用过程。这种方式下,如果信号的参数是个原始指针,那需要确保原始指针的生命周期直到事件被处理时仍然有效;其他类型(包括引用)的参数,qt会自动去克隆。还要注意的是,该类型下,信号的参数会放到event中,所以参数类型比如是QMetatype,如果不是,需要使用qRegisterMetaType()去注册。 - Qt::BlockingQueuedConnection
也是队列连接,不同于QueuedConnection在post事件之后立马返回,BlockingQueuedConnection在post事件之后,会一直阻塞直到该事件被处理。所以要注意,当发送者和接收者在同一线程时,千万布套使用这种方式,不然就会导致线程死锁。另外,虽然这是队列连接,但是由于是阻塞的,所以可以看成是同步的,也就不存在参数的生命周期问题。 - Qt::AutoConnection
自动连接,如果发送者和接收者在同一线程,那么就是DirectConnection,否则就使用QueuedConnection。 - Qt::UniqueConnection
- 严格来说,这并不是一种连接方式,而仅仅是一个掩码,表示唯一连接,它可以与上面四种连接方式以 | 的方式组合,比如 Qt::DirectConnection|Qt::UniqueConnection。如果发送者的信号已于接收者的槽连接过,再进行连接时就会失败。
QMetaMethod::MethodType
类中方法的类型
- QMetaMethod::Method
是用invoke修饰的成员函数 - QMetaMethod::Signal
信号 - QMetaMethod::Slot
槽函数 - QMetaMethod::Constructor
构造函数
方法签名
QMetaMethod的methodSignature()返回方法的签名,签名是个字符串。
下面是QMetaMethod获取方法签名的源码:
QByteArray QMetaMethodPrivate::signature() const
{
QByteArray result;
result.reserve(256);
result += name();
result += '(';
QList<QByteArray> argTypes = parameterTypes();
for (int i = 0; i < argTypes.size(); ++i) {
if (i)
result += ',';
result += argTypes.at(i);
}
result += ')';
return result;
}
可以看出签名实际是方法声明去掉返回值和参数名称后的字符串。
例如:
信号声明:
void clicked(bool state)
那么信号的签名就是clicked(bool)
。
connect函数源码解析
函数原型:
QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type)
五个参数:发送者,信号,接收者,槽函数或信号,连接类型
第一步:参数检查
if (sender == nullptr
|| receiver == nullptr
|| signal.methodType() != QMetaMethod::Signal
|| method.methodType() == QMetaMethod::Constructor) {
qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
sender ? sender->metaObject()->className() : "(nullptr)",
signal.methodSignature().constData(),
receiver ? receiver->metaObject()->className() : "(nullptr)",
method.methodSignature().constData() );
return QMetaObject::Connection(0);
}
发送者和接收者不能为空这是基本条件,另外,第二个参数必须是信号,第四个参数不能是构造函数(从第一节可以看出,除了构造函数外,还有信号、槽函数、普通成员函数三种类型)。
第二步:获取信号和槽的index
int signal_index;
int method_index;
{
int dummy;
QMetaObjectPrivate::memberIndexes(sender, signal, &signal_index, &dummy);
QMetaObjectPrivate::memberIndexes(receiver, method, &dummy, &method_index);
}
const QMetaObject *smeta = sender->metaObject();
const QMetaObject *rmeta = receiver->metaObject();
if (signal_index == -1) {
qWarning("QObject::connect: Can't find signal %s on instance of class %s",
signal.methodSignature().constData(), smeta->className());
return QMetaObject::Connection(0);
}
if (method_index == -1) {
qWarning("QObject::connect: Can't find method %s on instance of class %s",
method.methodSignature().constData(), rmeta->className());
return QMetaObject::Connection(0);
}
通过QMetaObjectPrivate::memberIndexes
获取信号或槽的index,然后再去检查index。
从moc后的代码来看,信号和槽的index,从信号开始,按声明顺序从0开始依次递增的排列,然后接着是槽声明的顺序,接着信号最后一个序号排列。
例如下面的类声明:
class MocTest : public QObject {
Q_OBJECT
public:
explicit MocTest(QObject* parent = nullptr);
signals:
void testSignal(const QString& name, int age);
public slots:
void testSlot(const QString& name, int age);
};
信号testSignal的index为0, 槽函数testSlot的index为1。
第三步:检查信号和槽的参数是否匹配
if (!QMetaObject::checkConnectArgs(signal.methodSignature().constData(), method.methodSignature().constData())) {
qWarning("QObject::connect: Incompatible sender/receiver arguments"
"\n %s::%s --> %s::%s",
smeta->className(), signal.methodSignature().constData(),
rmeta->className(), method.methodSignature().constData())
return QMetaObject::Connection(0);
}
这里的比较基于这样一个要求:槽函数参数个数≤信号参数个数,且参数类型和顺序必须一致。想想也很容易明白,槽函数参数少,那信号多出来的参数可以不传下去,但是如果槽函数参数多的话,那多出来的参数应该传什么值呢。
在第一节我们说过方法签名的组成:方法名称(参数类型1,参数类型2,…),下面看checkConnectArgs源码:
bool QMetaObject::checkConnectArgs(const char *signal, const char *method)
{
const char *s1 = signal;
const char *s2 = method;
while (*s1++ != '(') { } // scan to first '('
while (*s2++ != '(') { }
if (*s2 == ')' || qstrcmp(s1,s2) == 0) // method has no args or
return true; // exact match
int s1len = qstrlen(s1);
int s2len = qstrlen(s2);
if (s2len < s1len && strncmp(s1,s2,s2len-1)==0 && s1[s2len-1]==',')
return true; // method has less args
return false;
}
按上面提到的比较要求,比较参数列表的话,就是看括号里的参数列表字符串,如果槽函数参数列表字符串是信号参数列表字符串的子串或者相同,那ok,否则fail。
第四步:检查信号参数类型
int *types = 0;
if ((type == Qt::QueuedConnection)
&& !(types = queuedConnectionTypes(signal.parameterTypes())))
return QMetaObject::Connection(0);
在前面讲连接类型我们提到如果是队列连接,参数类型比如是QMetaType或者是注册到QMetaType。这一步就是检查参数类型是否是QMetaType。
那BlockingQueuedConnection也是队列连接,为啥不需要检查参数类型呢?
那是因为虽然BlockingQueuedConnection是队列连接,但是通过阻塞变成了同步调用,所以不存在参数生命周期问题,所以不会去克隆信号参数,所以也就不需要是QMetaType。
第五步:创建连接对象
QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
sender, signal_index, signal.enclosingMetaObject(), receiver, method_index, 0, type, types));
return handle;
所有的检查都通过后,就通过QMetaObjectPrivate::connect来创建连接对象。
这个函数主要分3小步:
检查Qt::UniqueConnection
QObjectPrivate::ConnectionData *scd = QObjectPrivate::get(s)->connections.loadRelaxed();
if (type & Qt::UniqueConnection && scd) {
if (scd->signalVectorCount() > signal_index) {
const QObjectPrivate::Connection *c2 = scd->signalVector.loadRelaxed()->at(signal_index).first.loadRelaxed();
int method_index_absolute = method_index + method_offset;
while (c2) {
if (!c2->isSlotObject && c2->receiver.loadRelaxed() == receiver && c2->method() == method_index_absolute)
return nullptr;
c2 = c2->nextConnectionList.loadRelaxed();
}
}
type &= Qt::UniqueConnection - 1;
}
如果连接类型带了UniqueConnection掩码,则检查当前是否符合UniqueConnection。
创建connection对象,并填充各个数据
std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};
c->sender = s;
c->signal_index = signal_index;
c->receiver.storeRelaxed(r);
QThreadData *td = r->d_func()->threadData;
td->ref();
c->receiverThreadData.storeRelaxed(td);
c->method_relative = method_index;
c->method_offset = method_offset;
c->connectionType = type;
c->isSlotObject = false;
c->argumentTypes.storeRelaxed(types);
c->callFunction = callFunction;
创建Connection对象,填充各个数据成员。
将connection对象添加到sender中
QObjectPrivate::get(s)->addConnection(signal_index, c.get());
将上一步创建的connection对象添加到sender的连接列表中。
到此,一个完整的connect执行流程就分析完成了。