5.QT5中的connect的实现

在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://qtguide.ustclug.org/

https://woboq.com/blog/how-qt-signals-slots-work-part2-qt5.html

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
PyQt5.QtChart 是 PyQt5 的一个模块,它提供了一些强大的图表组件,可以帮助我们快速实现各种类型的图表,包括动态曲线图。 下面是一个简单的示例,演示如何使用 PyQt5.QtChart 实现动态曲线图: ```python from PyQt5.QtWidgets import QApplication, QMainWindow, QGridLayout, QWidget from PyQt5.QtChart import QChart, QChartView, QLineSeries from PyQt5.QtCore import Qt, QTimer import random class MainWindow(QMainWindow): def __init__(self): super().__init__() # 创建曲线图和曲线 self.chart = QChart() self.series = QLineSeries() self.chart.addSeries(self.series) # 设置图表标题和坐标轴标签 self.chart.setTitle("Dynamic Curve") self.chart.setAnimationOptions(QChart.SeriesAnimations) self.chart.createDefaultAxes() self.chart.axes()[0].setTitleText("X") self.chart.axes()[1].setTitleText("Y") # 创建图表视图 self.chart_view = QChartView(self.chart) self.chart_view.setRenderHint(QPainter.Antialiasing) # 创建布局并添加图表视图 layout = QGridLayout() layout.addWidget(self.chart_view) # 创建窗口并设置布局 central_widget = QWidget() central_widget.setLayout(layout) self.setCentralWidget(central_widget) # 创建定时器并连接到更新函数 self.timer = QTimer(self) self.timer.timeout.connect(self.update_plot) self.timer.start(50) # 初始化计数器和数据列表 self.count = 0 self.data = [] def update_plot(self): # 更新计数器和数据列表 self.count += 1 self.data.append(random.randint(0, 100)) # 更新曲线数据 self.series.clear() for i in range(len(self.data)): self.series.append(i, self.data[i]) # 设置坐标轴范围 self.chart.axisX().setRange(max(0, self.count-100), self.count) self.chart.axisY().setRange(0, 100) if __name__ == '__main__': app = QApplication([]) window = MainWindow() window.show() app.exec_() ``` 在这个例子,我们创建了一个 QMainWindow 窗口,并在其添加了一个 QChartView 视图,用于显示动态曲线图。我们还创建了一个 QTimer 定时器,并将其连接到 update_plot() 函数,以定期更新曲线图的数据。 在 update_plot() 函数,我们生成一个随机数,并将其添加到数据列表。然后,我们清除曲线的所有数据,重新将数据添加到曲线。最后,我们根据数据的数量调整坐标轴的范围。 请注意,我们使用了一个计数器来记录数据的数量,以便我们可以根据需要滚动我们的曲线图。在这个例子,我们每次只显示最近的 100 个数据点。 运行这个示例程序,你将会看到一个动态曲线图,显示随机生成的数值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值