QT信号和槽机制实现及源码阅读

说明

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) {}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值