说明
QT的信号和槽采用观察者模式,Q_OBJECT是提供信号和槽的基础,使用过connect第五个参数的可知,槽可以在信号发出的线程处理,也可以加入任务队列进行处理。但是在此只写了在触发线程处理的代码
如下是实现的类QT信号和槽
/*************************************************************************
> File Name: qt_connect.cc
> Author: hsz
> Brief:
> Created Time: Wed 10 Aug 2022 09:08:14 AM CST
************************************************************************/
#include <iostream>
#include <vector>
#include <string>
template<typename Func> struct FunctionPointer { };
// 类的成员函数
template<class Obj, typename Ret, typename... Args>
struct FunctionPointer<Ret (Obj::*) (Args...)>
{
typedef Obj Object;
typedef Ret ReturnType;
typedef Ret (Obj::*Function) (Args...);
static void call(Function f, Obj *o, Args... args) {
(o->*f)(std::forward<Args>(args)...);
}
};
// 类的成员函数 const
template<class Obj, typename Ret, typename... Args>
struct FunctionPointer<Ret (Obj::*) (Args...) const>
{
typedef Obj Object;
typedef Ret ReturnType;
typedef Ret (Obj::*Function) (Args...) const;
static void call(Function f, Obj *o, Args... args) {
(o->*f)(std::forward<Args>(args)...);
}
};
// 普通函数
template<typename Ret, typename... Args>
struct FunctionPointer<Ret (*) (Args...)>
{
typedef Ret ReturnType;
typedef Ret (*Function) (Args...);
static void call(Function f, Args... args) {
f(std::forward<Args>(args)...);
}
};
template<typename... Args>
class SlotBase
{
public:
virtual ~SlotBase() = default;
void exec(Args... args)
{
slotFunction(std::forward<Args>(args)...);
}
protected:
virtual void slotFunction(Args... args) = 0;
};
template<typename TRecver, typename SlotFunc, typename... Args>
class Slot : public SlotBase<Args...>
{
public:
typedef FunctionPointer<SlotFunc> FuncType;
Slot(TRecver *recver, SlotFunc func)
{
mRecver = recver;
mFunc = func;
}
virtual ~Slot() {}
protected:
virtual void slotFunction(Args... args) override
{
FuncType::call(mFunc, mRecver, std::forward<Args>(args)...);
}
private:
TRecver *mRecver;
SlotFunc mFunc;
};
template<typename... Args>
class Signaler
{
public:
Signaler() {}
~Signaler() {}
template<typename TRecver, typename SlotFunc>
void addSlot(TRecver *recver, SlotFunc func)
{
mSlotVec.push_back(new Slot<TRecver, SlotFunc, Args...>(recver, func));
}
void operator()(Args... args)
{
for (SlotBase<Args...> *it : mSlotVec)
{
it->exec(args...);
}
}
private:
std::vector<SlotBase<Args...> *> mSlotVec;
};
class Recv1
{
public:
void func(int num, std::string str)
{
printf("Recv1::func(%d, %s)\n", num, str.c_str());
}
};
class Recv2
{
public:
void func(int num, std::string str)
{
printf("Recv2::func(%d, %s)\n", num, str.c_str());
}
};
class Send
{
public:
Signaler<int, std::string> valueChanged;
public:
void test(int num, std::string str)
{
printf("%p\n", str.c_str());
valueChanged(num, str);
}
};
#define connect(sender, signal, recver, slot) \
(sender)->signal.addSlot(recver, slot);
int main(int argc, char **argv)
{
Send send;
Recv1 r1;
Recv2 r2;
connect(&send, valueChanged, &r1, &Recv1::func);
connect(&send, valueChanged, &r2, &Recv2::func);
send.test(10, std::string("Hello"));
return 0;
}
运行结果
QT实现
QT的信号和槽需要moc进行处理,生成moc_xxx.cpp文件,里面存放的是一些meta相关的初始化函数实现及信号函数的实现。所以信号函数只需声明,而不需要自己实现。
QT的多参是通过模板实现的,如上的FunctionPointer就是QT中的源码,只不过我在此代码中进行了不必要的简化。
1、先来说说信号函数如何传递返回值及参数
// 从某一个moc文件截取的信号函数
int MainWindow::valueChange(uint16_t _t1, double _t2)
{
int _t0{};
void *_a[] = {
const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t0))),
const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))),
const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
return _t0;
}
/*
如上便是一个信号函数的实现,可以看见其参数为uint16_t和double及int返回值
其中_a数组保存的就是这三个参数的地址。staticMetaObject是Q_OBJECT宏声明的
activate是调用槽函数
_a数组内容:返回值(void时为nullptr), 参数1地址, 参数2地址, ...
当无参无返回值时就是含有一个nullptr的数组
由此可知,触发信号其实是调用的信号函数,至于emit宏,其本质是个空的,预处理后就没了,
QT加上它是方便程序员看代码,看到emit就知道这是个信号,没有这个emit则会以为是某一处理函数
*/
2、如何调用到槽函数
不管是什么样的信号函数,其都会调用到如下activate
// qobject.cpp
void QMetaObject::activate(QObject *sender, int signalOffset,
int local_signal_index, void **argv);
跳过一些条件判断来到具体执行函数
首先是对信号加锁,所以极短时间触发两次信号会导致第二次信号阻塞。
(这个锁并不是meta自带的,而是取自锁池,获取某一个锁还和sender地址相关)
// 没看懂为什么这么设计
// 一个大型的项目,其sender肯定超过131个,所以某两个sender势必会取到同一个锁,
// 当两个同时触发会导致某一个会阻塞住,这样是不是也就可以解释当在ui线程中处理一个耗时任务时,
// 在拖拽窗口会出现弹出无响应框
QMutexLocker locker(signalSlotLock(sender));
static QBasicMutex _q_ObjectMutexPool[131];
/**
* \internal
* mutex to be locked when accessing the connectionlists or the senders list
*/
static inline QMutex *signalSlotLock(const QObject *o)
{
return static_cast<QMutex *>(&_q_ObjectMutexPool[
uint(quintptr(o)) % sizeof(_q_ObjectMutexPool)/sizeof(QBasicMutex)]);
}
继续往下走可以看到声明了一个结构体,这个结构体的作用类似智能指针,管理inUse这个变量
QObjectConnectionListVector 这个类存储是一个object的所有QMetaObject::Connection对象(connect返回值就是QMetaObject::Connection),他是一个数组加链表结构,数组的索引是某一信号,链表是具体存储这一信号所connect的所有QMetaObject::Connection对象,如下
信号的索引 = signalOffset + local_signal_index;
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; }
};
知道了信号的索引位置就可以取到这个connection链表
接下来就是遍历链表,执行槽函数(由于代码过多,只放一下流程代码)
int signal_index = signalOffset + local_signal_index;
ConnectionListsRef connectionLists = sender->d_func()->connectionLists;
list = &connectionLists->at(signal_index);
do {
QObjectPrivate::Connection *c = list->first;
if (!c) continue;
QObjectPrivate::Connection *last = list->last;
do {
QObject * const receiver = c->receiver;
// 这个就是判断connect第五参数的类型,是否在本线程执行槽函数还是加入线程队列
if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
|| (c->connectionType == Qt::QueuedConnection)) {
queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker);
continue;
}
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和智能指针一样,重载了指针运算符,此处是QtPrivate::QSlotObjectBase对象
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();
}
} while (c != last && (c = c->nextConnectionList) != 0);
} while (list != &connectionLists->allsignals &&
((list = &connectionLists->allsignals), true));
}
// allsignals是一个无效list,因为信号可能触发了,但是没有connect对应的槽函数,所以就会返回这个链表
QSlotObjectBase是虚基类,call调用的是impl这个函数,FunctionPointer这个结构体的作用是可以接收不同种类的函数,成员函数,const成员函数,普通函数等等,至此,触发信号到调用到槽函数的流程已说明完毕
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() Q_DECL_NOTHROW { return m_ref.ref(); }
inline void destroyIfLastRef() Q_DECL_NOTHROW
{ 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(QSlotObjectBase)
};
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) {}
};