在QT4中,解析信号槽是通过将信号槽的名字转化为字符串,然后通过connect解析该字符串,得到信号函数的相对序号和,然后创建信号connectionlist,但是,所有的检查都在运行时,通过解析字符串进行。 这意味着,如果信号槽的名称拼写错误,编译会成功,但是只是会建立空连接。
moctest::moctest()
{
connect(this, SIGNAL(sigf1(double1)) , this, SLOT(slotf(double)));
connect(this, &moctest::sigf2, this, &moctest::slotf2);
}
上述代码编译时,不会出现问题,但是在运行时,会提示错误
也就是说,当使用SIGNAL和SLOT后,无论宏里面写啥,编译时都不会检查
所以,为了添加编译时的检查,新版的connect使用了函数指针作为信号槽函数,使用了函数指针后,如果信号槽函数出现拼写错误,则会在编译时无法通过,而且还可以使用lambda表达式槽函数,因为lambda表达式返回的也是一个函数指针。
当函数指针拼写错误时,QT在编译前就会提示找不到成员函数
新版connect的函数声明
template<typename PointerToMemberFunction>
static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection);
使用connect时,一般是这样的
connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);
第二个和第四个是函数指针,将模板参数PointerToMemberFunction变成函数的类型
connect的具体实现如下
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);
}
先看函数名
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)
其中Func1和Func2是模板参数,表示成员函数的类型,QtPrivate::FunctionPointer<Func1>::Object *sender是发送信号的对象,signal是函数指针,后两个参数同理
QtPrivate::FunctionPointer的实现在qobjectdefs_impl.h中,该模板类主要用来提供元数据
template<class Obj, typename Ret, typename... Args> struct FunctionPointer<Ret (Obj::*) (Args...)>
{
typedef Obj Object;//包含成员函数的类
typedef List<Args...> Arguments;//代表参数列表。
typedef Ret ReturnType;
typedef Ret (Obj::*Function) (Args...);
enum {ArgumentCount = sizeof...(Args), IsPointerToMemberFunction = true};//ArgumentCount表示函数的参数数目,
template <typename SignalArgs, typename R>
static void call(Function f, Obj *o, void **arg) {//使用给定参数调用该成员函数f。
FunctorCall<typename Indexes<ArgumentCount>::Value, SignalArgs, R, Function>::call(f, o, arg);
}
};
然后是两个typedef
typedef QtPrivate::FunctionPointer<Func1> SignalType;
typedef QtPrivate::FunctionPointer<Func2> SlotType;
当把信号函数和槽函数传入后,根据FunctionPointer会解析出信号槽函数的返回值、所在类和形参列表
紧接着是四个Q_STATIC_ASSERT_X断言,必须包含Q_OBJECT,信号的参数数量必须大于等于槽函数的参数数量,信号的参数和槽函数的参数必须得兼容(类型一致或可以转化),信号槽的返回值的类型也必须得兼容
之后是如果连接方式是QueuedConnection或者BlockingQueuedConnection,那么要获取入队的参数信息,同QT4
最后执行connectImpl
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);
这里面用new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value, typename SignalType::ReturnType>(slot)初始化QtPrivate::QSlotObjectBase *slotObj
Func2表示槽函数的类型
typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,这里面,List_Left <L,N>以一个列表和一个数字作为参数并返回一个列表,列表由列表的前N个元素组成,所以这里面返回的是一个列表,列表里有好多类型,信号的参数个数有可能多于SlotType::ArgumentCount,但是最多取SlotType::ArgumentCount个
typename SignalType::ReturnType表示信号函数的返回值类型
指定完三个模板参数后,传入槽函数的指针slot
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 nullptr 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);//调用MOC文件中的static_metacall计算信号的相对序号
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);//继续调用QObjectPrivate::connectImpl
}
思路依旧是获取信号的绝对序号,和QT4类似
QObjectPrivate::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 nullptr parameter", senderString, receiverString);
if (slotObj)
slotObj->destroyIfLastRef();
return QMetaObject::Connection();
}//同QT4
QObject *s = const_cast<QObject *>(sender);
QObject *r = const_cast<QObject *>(receiver);
QOrderedMutexLocker locker(signalSlotLock(sender),
signalSlotLock(receiver));
if (type & Qt::UniqueConnection && slot && QObjectPrivate::get(s)->connections.loadRelaxed()) {//使得每个连接都是UniqueConnection,和QT4类似
QObjectPrivate::ConnectionData *connections = QObjectPrivate::get(s)->connections.loadRelaxed();
if (connections->signalVectorCount() > signal_index) {
const QObjectPrivate::Connection *c2 = connections->signalVector.loadRelaxed()->at(signal_index).first.loadRelaxed();
while (c2) {
if (c2->receiver.loadRelaxed() == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) {
slotObj->destroyIfLastRef();
return QMetaObject::Connection();
}
c2 = c2->nextConnectionList.loadRelaxed();
}
}
type = static_cast<Qt::ConnectionType>(type ^ Qt::UniqueConnection);
}
std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};//创建一个Connection,和QT4类似
c->sender = s;
c->signal_index = signal_index;
QThreadData *td = r->d_func()->threadData;
td->ref();
c->receiverThreadData.storeRelaxed(td);
c->receiver.storeRelaxed(r);
c->slotObj = slotObj;//区别,这里使用的是基类QSlotObjectBase和子类QSlotObject来填充connection结构,其余的和QT4类似
c->connectionType = type;
c->isSlotObject = true;
if (types) {
c->argumentTypes.storeRelaxed(types);
c->ownArgumentTypes = false;
}
QObjectPrivate::get(s)->addConnection(signal_index, c.get());
QMetaObject::Connection ret(c.release());
locker.unlock();
QMetaMethod method = QMetaObjectPrivate::signal(senderMetaObject, signal_index);
Q_ASSERT(method.isValid());
s->connectNotify(method);
return ret;
}
上述两段代码和QT4基本相同,都是计算信号的绝对序号,然后创建信号的connectionlist并添加,和QT4不同的是,connection的采用的是基类QSlotObjectBase和子类QSlotObject来填充connection结构,而不是直接使用函数指针
QSlotObjectBase和QSlotObject的定义在qobjectdefs_impl.h中
class QSlotObjectBase {
QAtomicInt m_ref;
// don't use virtual functions here; we don't want the
// compiler to create tons of per-polymorphic-class stuff that
// we'll never need. We just use one function pointer.
typedef void (*ImplFn)(int which, QSlotObjectBase* this_, QObject *receiver, void **args, bool *ret);//函数指针
const ImplFn m_impl;
protected:
enum Operation {
Destroy,
Call,
Compare,
NumOperations
};
public:
explicit QSlotObjectBase(ImplFn fn) : m_ref(1), m_impl(fn) {}
inline int ref() noexcept { return m_ref.ref(); }
inline void destroyIfLastRef() noexcept
{ if (!m_ref.deref()) m_impl(Destroy, this, nullptr, nullptr, nullptr); }
inline bool compare(void **a) { bool ret = false; m_impl(Compare, this, nullptr, a, &ret); return ret; }
inline void call(QObject *r, void **a) { m_impl(Call, this, r, a, nullptr); }
protected:
~QSlotObjectBase() {}
private:
Q_DISABLE_COPY_MOVE(QSlotObjectBase)
};
// implementation of QSlotObjectBase for which the slot is a pointer to member function of a QObject
// Args and R are the List of arguments and the returntype of the signal to which the slot is connected.
template<typename Func, typename Args, typename R> class QSlotObject : public QSlotObjectBase
{
typedef QtPrivate::FunctionPointer<Func> FuncType;
Func function;
static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret)
{
switch (which) {
case Destroy://
delete static_cast<QSlotObject*>(this_);
break;
case Call:
FuncType::template call<Args, R>(static_cast<QSlotObject*>(this_)->function, static_cast<typename FuncType::Object *>(r), a);
break;
case Compare:
*ret = *reinterpret_cast<Func *>(a) == static_cast<QSlotObject*>(this_)->function;
break;
case NumOperations: ;
}
}
public:
explicit QSlotObject(Func f) : QSlotObjectBase(&impl), function(f) {}//QSlotObject的构造函数将槽函数的地址传入并初始化function,使得能正确调用对应的槽函数
};
如果使用虚函数,每个实例都需要创建一个虚拟表,该表不仅包含指向虚拟函数的指针,而且还包含许多我们不需要的信息,这将导致大量多余的数据。所以,将QSlotObjectBase设置为普通类。QSlotObject的构造函数将槽函数的地址传入并初始化function,使得能正确调用对应的槽函数
参考
Qt5.14源码
https://woboq.com/blog/how-qt-signals-slots-work-part2-qt5.html
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出