精华内容
下载资源
问答
  • Qt信号槽机制

    2020-11-23 18:40:27
    Qt信号槽机制 文章目录Qt信号槽机制概述信号槽 概述 信号槽机制是Qt中非常标志性的工具。信号和槽本质上都是函数,只不过是更为特殊的函数。 信号槽的简单理解就是某个对象发出信号,而拥有槽的对象接受信号并进行...

    Qt信号槽机制

    概述

    信号槽机制是Qt中非常标志性的工具。信号和槽本质上都是函数,只不过是更为特殊的函数。

    信号槽的简单理解就是某个对象发出信号,而拥有槽的对象接受信号并进行某些操作。但是并不一定每一个信号必须有对应的槽,也并不一定每个槽都需要有信号,二者的耦合性非常低。

    可以在任何地方发送信号,不需要知道哪些类发送的信号连接到了哪些类的槽,只需要将信号和槽连接,当某个信号发送时,与之相连的槽(们)就会被执行(和普通的函数调用一样)。

    信号和槽之间的关系可以是一对一、一对多、多对一、多对多的关系。

    前面提到,信号和槽本质上都是函数,函数就会有函数声明和函数定义,二者的区别稍后再说。这里应该明确的是,信号和槽之间除了响应和被响应的关系,还有数据传递的功能,而参数就是用于参数传递的,一般而言,信号和槽的参数类型应当完全一致,但是槽函数的参数可以少一些,信号多出来的参数将会被忽略。

    这里可能会有疑问,槽函数如果是缺省函数,是不是参数就会比信号函数多呢?Qt对此也进行了规定,因为信号和槽都是在运行时进行绑定的,因此缺省函数的实现将会很困难,所以规定槽函数和信号函数都不能使用缺省值

    信号

    信号的作用只有一个,就是告知槽函数应该执行了,并且带有槽函数所需要的参数。

    信号本身也就是个函数,但是这个函数只能有声明,而绝对不能有定义(也没必要有定义)。

    信号函数不能有返回值(void),并且参数列表必须与槽函数一致(只能多不能少)。

    在类中,我们通过:

    signals:
    	void funcName(parameter list);
    

    来定义信号,其实Qt自带很多信号,比如点击信号(这是最基本的信号之一),这里写的是自定义的信号。

    前面说了,信号可以在任何地方被发送,信号发送的语句为:

    emit funcName(parameters);
    

    当信号发送时,槽函数会立即执行,和普通函数调用一样,此时信号和槽会独立于事件循环之外,当槽函数执行完成返回之后,跟在emit之后的语句会继续执行,如果绑定了多个槽,则会按照connect的顺序依次执行槽函数,connect就是连接信号和槽的一个函数。

    举例(修改自官方文档):

    signals:
        void send(int value);
    

    当绑定到槽的信号被发送之后,就会执行槽函数。

    槽函数实际上和普通函数没有什么差别,但是槽函数可以被任何发送信号的对象调用,不会受到可见性的制约(访问限制)。

    槽函数也可以被定义为虚函数,在某些情况下会非常有效(我目前也还在学习,还不清楚什么时候使用虚函数来定义槽函数比较好)。

    前面说,槽函数和普通函数没什么差别,除了参数列表要和信号保持一致之外,没有什么特别约束。槽函数也可以有返回值,也可以被普通调用,唯一不同的就是除了普通调用,还会对信号做出回应。

    槽函数声明举例:

    public slots:
    	void funcNameA(parameter list);
    	int funcNameB(parameter list);
    	...
    

    举例(修改自官方文档):

    public slots:
        bool setValue(int value);
    

    我们定义好信号和槽之后,还需要将二者连接在一起,有很多种方法,常见方法是使用QObject::connect函数,推荐到官方文档查看这个函数的具体用法,因为官方文档真的写的非常非常详细简洁。

    QObject::connect官方文档网址

    举例(修改自官方文档):

    TextFinder b, c;
    
    QObject::connect(&b, &TextFinder::send, &c, &TextFinder::setValue);
    b.setValue(12);
    
    展开全文
  • Qt信号和槽

    2021-04-18 13:14:24
    Qt信号和槽信号和槽简介信号和槽SignalsSlots一个小例子一个真实的例子带有默认参数的信号和槽高级信号和槽使用将Qt与第三方信号和槽一起使用 信号和槽 信号和槽用于对象之间的通信。信号和时隙机制是Qt的主要功能,...

    信号和槽

    信号和槽用于对象之间的通信。信号和时隙机制是Qt的主要功能,可能是与其他框架提供的功能最大不同的部分。Qt的元对象系统使信号和时隙成为可能。

    简介

    在GUI编程中,当我们更改一个小部件时,我们通常希望通知另一个小部件。更笼统地说,我们希望任何类型的对象都能够相互通信。例如,如果用户单击“关闭”按钮,我们可能希望调用窗口的close()函数。

    其他工具包使用回调来实现这种通信。回调是指向函数的指针,因此,如果您希望处理函数将某些事件通知您,则可以将指向另一个函数的指针(回调)传递给处理函数。然后,处理函数将在适当时调用回调。尽管确实存在使用此方法的成功框架,但回调可能不直观,并且可能在确保回调参数的类型正确性方面遇到问题。

    展开全文
  • 【QT】深入qt信号与槽实现原理

    万次阅读 2017-07-01 05:19:38
    1、先上示例代码先上示例代码直观地感受一下qt信号与槽的用法,后面再详细解释。通过QtCreator创建一个Qt Widget工程(没有创建ui文件,其它选项为默认值),工程名为SS,最后在SS目录下会生成5个文件:main.cpp、...

    1、先上示例代码

    先上示例代码直观地感受一下qt信号与槽的用法,后面再详细解释。通过QtCreator创建一个Qt Widget工程(没有创建ui文件,其它选项为默认值),工程名为SS,最后在SS目录下会生成5个文件:main.cpp、mainwindow.cpp、mainwindow.h、SS.pro和SS.pro.user,然后对这几个文件稍作修改,最终的源码如下。

    SS.pro——

    QT += core gui
    QT += widgets
    
    TARGET = SS
    TEMPLATE = app
    
    SOURCES += main.cpp\
            mainwindow.cpp
    
    HEADERS  += mainwindow.h

    SS.pro.user——
    这是一个xml文件,保存了SS工程在QtCreator中的相关配置信息,不是我们关注的对象。

    main.cpp——

    #include "mainwindow.h"
    
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
    
        return a.exec();
    }

    mainwindow.h——

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
    protected:
        void mousePressEvent(QMouseEvent *event);
    
    //Q_SIGNALS:
    signals:
        void mousePressed();
    
    //private Q_SLOTS:
    private slots:
        void onMousePressed();
    };
    
    #endif // MAINWINDOW_H

    mainwindow.cpp——

    #include "mainwindow.h"
    
    #include <QDebug>
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
    {
    //    connect(this, &MainWindow::mousePressed, this, &MainWindow::onMousePressed);
        connect(this, SIGNAL(mousePressed()), this, SLOT(onMousePressed()));
    
        setGeometry(100, 100, 360, 360);
    }
    
    MainWindow::~MainWindow()
    {
        disconnect(this, SIGNAL(mousePressed()), this, SLOT(onMousePressed()));
    }
    
    void MainWindow::mousePressEvent(QMouseEvent *event)
    {
        Q_UNUSED(event)
    
        emit mousePressed(); // Q_EMIT
    }
    
    void MainWindow::onMousePressed()
    {
        qDebug() << "[SLOT] MainWindow::onMousePressed";
    }

    接着,通过QtCreator编译SS工程,在形如build-SS-XXX-XXX的目录下产生编译结果,共6个文件:Makefile、SS、main.o、mainwindow.o、moc_mainwindow.o和moc_mainwindow.cpp,且不管这些文件是如何生成的,有什么作用。

    最后,通过QtCreator运行SS工程,在弹出的窗口上按下鼠标就会输出“[SLOT] MainWindow::onMousePressed”,这个log就是通过信号与槽实现的。

    2、信号与槽简介

    信号(signal)与槽(slot)是qt的一大特色,由元对象系统(meta object system)提供,用于对象间的通信,类似的还有借助于函数指针的回调机制,理论上,信号与槽比回调的反应速度要慢,但前者用起来更灵活。下面以SS工程为例,简单介绍一下信号与槽的用法。SS.pro为工程文件,main.cpp文件实现了必需的main函数,这两个文件不作更多解释,重点在于mainwindow.h和mainwindow.cpp。

    使用信号与槽,首先,类必须直接或间接继承自QObject,如示例中的MainWindow继承自QMainWindow,而QMainWindow间接继承自QObject;然后,在类入口处使用O_OBJECT宏,这是必须的;接着,使用signals或Q_SIGNALS声明信号,如示例中的mousePressed,信号类似于成员函数,只不过其返回类型一般为void,但可以有参数,而且只有声明不需定义,使用private、protected或public slots或Q_SLOTS声明槽并定义槽,如示例中的onMousePressed,槽就是个普通的成员函数,只不过声明时多了个slots或Q_SLOTS而已;最后使用connect连接信号与槽,信号与信号也可以连接,当信号发送时,就会触发与之连接的槽,使用disconnect断开连接,两者连接时它们的参数列表必须相同,示例中在构造函数中connect,析构函数中disconnect,重写了虚函数mousePressEvent,当有鼠标按下事件时就会调到这个函数,函数中通过emit发送mousePressed信号,进而触发与之连接的onMousePressed槽,输出log。connect和disconnect有多个重载函数,这里不作详细介绍,其中connect连接的信号与槽可以通过取地址符直接取对应的地址,或者使用SIGNAL与SLOT进行包装,但后者更好用。

    3、编译过程分析(Qt5)

    上面提到了编译结果,有两个文件比较奇怪,moc_mainwindow.cpp和moc_mainwindow.o。首先,通过qmake及其默认配置和SS.pro生成Makefile,然后,通过这个Makefile继续编译。接下来,使用g++编译main.cpp生成main.o,使用g++编译mainwindow.cpp生成mainwindow.o,使用元对象编译器moc编译mainwindow.h生成moc_mainwindow.cpp,使用g++编译moc_mainwindow.cpp生成moc_mainwindow.o,最后使用g++链接main.o、mainwindow.o和moc_mainwindow.cpp和生成SS,over。重点在于moc,我们来看一下由moc生成的moc_mainwindow.cpp是如何保存信号与槽相关信息的。

    qt_meta_stringdata_MainWindow变量——

    struct qt_meta_stringdata_MainWindow_t {
        QByteArrayData data[4];
        char stringdata[40];
    };
    #define QT_MOC_LITERAL(idx, ofs, len) \
        Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
        qptrdiff(offsetof(qt_meta_stringdata_MainWindow_t, stringdata) + ofs \
            - idx * sizeof(QByteArrayData)) \
        )
    static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {
        {
    QT_MOC_LITERAL(0, 0, 10), // "MainWindow"
    QT_MOC_LITERAL(1, 11, 12), // "mousePressed"
    QT_MOC_LITERAL(2, 24, 0), // ""
    QT_MOC_LITERAL(3, 25, 14) // "onMousePressed"
    
        },
        "MainWindow\0mousePressed\0\0onMousePressed"
    };
    #undef QT_MOC_LITERAL

    qt_meta_stringdata_MainWindow是一个只读的静态变量,变量名中qt_meta_stringdata_为固定字段,MainWindow为对应的类名。qt_meta_stringdata_MainWindow的类型为qt_meta_stringdata_MainWindow_t,是个结构体,有两个数组成员,每个数组的长度是都是动态的。数组data有4个元素,元素排列顺序为当前类、第一个信号、占位符、其它信号、其它槽,信号在槽前面,信号和槽各自的顺序以声明的顺序排列,示例中MainWindow有1个信号和1个槽,所以加上类和占位符共4个元素,至少有1个信号或槽时后面就有1个占位符,否则只有当前类1个元素;每个元素都使用了QT_MOC_LITERAL参数宏,第一个参数表示元素索引,第二个参数表示元素在stringdata中的偏移量,第三个参数表示元素对应的字符串长度,实际上就是对QByteArrayData进行初始化,详细分析见下面的“QByteArrayData初始化”。stringdata是个字符数组,长度与data数组中的元素有关,顺序保存了data数组中各元素对应的字符串表示,即类名、信号名和槽名,占位符不占据任何长度,各个字段之间以空(null)字符分隔,示例中这个值为"MainWindow\0mousePressed\0\0onMousePressed"

    QByteArrayData初始化——
    下面深挖QByteArrayData结构及初始化方式,顺便学习下C++强大的模板用法,如下层层展开的代码所示。

    // 1. QByteArrayData is QArrayData with 5 data members
    typedef QArrayData QByteArrayData;
    struct Q_CORE_EXPORT QArrayData
    {
        QtPrivate::RefCount ref; // see below
        int size; // int
        uint alloc : 31; // unsigned int with 31 bits
        uint capacityReserved : 1; // unsignet int with 1 bit
        qptrdiff offset; // in bytes from beginning of header // see below
        // others ...
    };
    // 2. What is QtPrivate::RefCount
    namespace QtPrivate
    {
    class RefCount
    {
    public:
        // others ...
        QBasicAtomicInt atomic; // typedef QBasicAtomicInteger<int> QBasicAtomicInt;
    };
    }
    // 2.1 QBasicAtomicInteger
    template <typename T>
    class QBasicAtomicInteger
    {
    public:
        typedef QAtomicOps<T> Ops;
        typename Ops::Type _q_value; // >> ref of QArrayData stored here (type is int) <<
        // others ...
    };
    // 2.2 QAtomicOps
    template <typename T> struct QAtomicOps : QBasicAtomicOps<sizeof(T)>
    {
        typedef T Type;
    };
    // 2.3 QBasicAtomicOps
    template <int size> struct QBasicAtomicOps : QGenericAtomicOps<QBasicAtomicOps<size> >
    {
        // something ...
    };
    // 2.4 QGenericAtomicOps
    template <typename BaseClass> struct QGenericAtomicOps
    {
        // something ...
    };
    // 3. What is qptrdiff
    typedef QIntegerForSizeof<void*>::Signed qptrdiff; // qint32(int - 32 bit signed) or qint64(long long - 64 bit signed)
    template <class T> struct QIntegerForSizeof: QIntegerForSize<sizeof(T)> { };
    template <int> struct QIntegerForSize;
    template <>    struct QIntegerForSize<1> { typedef quint8  Unsigned; typedef qint8  Signed; };
    template <>    struct QIntegerForSize<2> { typedef quint16 Unsigned; typedef qint16 Signed; };
    template <>    struct QIntegerForSize<4> { typedef quint32 Unsigned; typedef qint32 Signed; };
    template <>    struct QIntegerForSize<8> { typedef quint64 Unsigned; typedef qint64 Signed; };
    // 4. What is QT_MOC_LITERAL
    #define QT_MOC_LITERAL(idx, ofs, len) \
        Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
        qptrdiff(offsetof(qt_meta_stringdata_MainWindow_t, stringdata) + ofs \
            - idx * sizeof(QByteArrayData)) \
        )
    // 4.1 macro definations
    #define Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset) \
        Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset)
    #define Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset) \
        { Q_REFCOUNT_INITIALIZE_STATIC, size, 0, 0, offset } \
    #define Q_REFCOUNT_INITIALIZE_STATIC { Q_BASIC_ATOMIC_INITIALIZER(-1) }
    #  define Q_BASIC_ATOMIC_INITIALIZER(a) { (a) }
    // 4.2 What is offsetof
    // offsetof from sqlite3.c
    #ifndef offsetof
    #define offsetof(STRUCTURE,FIELD) ((int)((char*)&((STRUCTURE*)0)->FIELD))
    #endif
    // offsetof from stddef.h
    #define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)

    qt_meta_data_MainWindow变量——

    static const uint qt_meta_data_MainWindow[] = {
    
     // content:
           7,       // revision 7 is Qt 5
           0,       // classname
           0,    0, // classinfo count and data
           2,   14, // methods count and data
           0,    0, // properties count and data
           0,    0, // enums/sets count and data
           0,    0, // constructors count and data
           0,       // flags since revision 3
           1,       // signalCount since revision 4
    
     // signals: name, argc, parameters, tag, flags
           1,    0,   24,    2, 0x06 /* Public */,
    
     // slots: name, argc, parameters, tag, flags
           3,    0,   25,    2, 0x08 /* Private */,
    
     // signals: parameters
        QMetaType::Void,
    
     // slots: parameters
        QMetaType::Void,
    
           0        // eod
    };

    qt_meta_data_MainWindow变量中数据类型为unit,有几个关键的地方,content中methods为2表示共有2个信号和槽,signalCount为1表示共有1个信号,接着是信号和槽的相关信息,最后一个元素为0标记结束。

    4、有用的宏

    示例中的宏Q_OBJECT、signals、Q_SIGNALS、slots、Q_SLOTS、emit等是非常有用的,在头文件qobjectdefs.h中定义,根据是否为moc编译而分为两个版本,源码如下。

    // The following macros are our "extensions" to C++
    // They are used, strictly speaking, only by the moc.
    #ifndef Q_MOC_RUN
    #ifndef QT_NO_META_MACROS
    # if defined(QT_NO_KEYWORDS)
    #  define QT_NO_EMIT
    # else
    #   ifndef QT_NO_SIGNALS_SLOTS_KEYWORDS
    #     define slots
    #     define signals public
    #   endif
    # endif
    # define Q_SLOTS
    # define Q_SIGNALS public
    # define Q_EMIT
    #ifndef QT_NO_EMIT
    # define emit
    #endif
    // others ...
    #endif // QT_NO_META_MACROS
    /* qmake ignore Q_OBJECT */
    #define Q_OBJECT \
    public: \
        Q_OBJECT_CHECK \
        static const QMetaObject staticMetaObject; \
        virtual const QMetaObject *metaObject() const; \
        virtual void *qt_metacast(const char *); \
        QT_TR_FUNCTIONS \
        virtual int qt_metacall(QMetaObject::Call, int, void **); \
    private: \
        Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
        struct QPrivateSignal {};
    #else // Q_MOC_RUN
    #define slots slots
    #define signals signals
    #define Q_SLOTS Q_SLOTS
    #define Q_SIGNALS Q_SIGNALS
    #define Q_OBJECT Q_OBJECT
    #endif //Q_MOC_RUN

    关键在于上面的Q_OBJECT,其中声明的函数由moc编译时实现,另外还实现了信号,前面提到了信号只声明不定义,其实信号也是函数,只不过由moc实现,示例中的moc_mainwindow.cpp相关源码及分析如下。

    // qt_static_metacall函数从其名字来看是一个调用函数的方法
    // 参数_c值为InvokeMetaMethod时说明将调用函数
    // 然后根据参数_id值去调用对应的信号或槽
    // 参数_c值为IndexOfMethod时通过成员指针对信号地址进行检查
    // 返回值为信号对应的_id
    void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
    {
        if (_c == QMetaObject::InvokeMetaMethod) {
            MainWindow *_t = static_cast<MainWindow *>(_o);
            switch (_id) {
            case 0: _t->mousePressed(); break;
            case 1: _t->onMousePressed(); break;
            default: ;
            }
        } else if (_c == QMetaObject::IndexOfMethod) {
            int *result = reinterpret_cast<int *>(_a[0]);
            void **func = reinterpret_cast<void **>(_a[1]);
            {
                typedef void (MainWindow::*_t)();
                if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&MainWindow::mousePressed)) {
                    *result = 0;
                }
            }
        }
        Q_UNUSED(_a);
    }
    // staticMetaObject变量保存了所有的元数据
    const QMetaObject MainWindow::staticMetaObject = {
        { &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow.data,
          qt_meta_data_MainWindow,  qt_static_metacall, Q_NULLPTR, Q_NULLPTR}
    };
    // metaObject函数用于获取QMetaObject
    const QMetaObject *MainWindow::metaObject() const
    {
        return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
    }
    // qt_metacast函数用于提取参数_clname对应类的信号与槽的名字
    // 因为qt_meta_stringdata_MainWindow.stringdata的第一个数据段保存的是类名
    // 所以可以通过strcmp进行类名比较
    void *MainWindow::qt_metacast(const char *_clname)
    {
        if (!_clname) return Q_NULLPTR;
        if (!strcmp(_clname, qt_meta_stringdata_MainWindow.stringdata))
            return static_cast<void*>(const_cast< MainWindow*>(this));
        return QMainWindow::qt_metacast(_clname);
    }
    // qt_metacall函数根据参数_id及_c执行不同的动作
    // 当_id<2且-c==InvokeMetaMethod时
    // 执行前面介绍的qt_static_metacall
    // 这里的数字2表示的是信号和槽的总数为2
    int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
    {
        _id = QMainWindow::qt_metacall(_c, _id, _a);
        if (_id < 0)
            return _id;
        if (_c == QMetaObject::InvokeMetaMethod) {
            if (_id < 2)
                qt_static_metacall(this, _c, _id, _a);
            _id -= 2;
        } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
            if (_id < 2)
                *reinterpret_cast<int*>(_a[0]) = -1;
            _id -= 2;
        }
        return _id;
    }
    // SIGNAL 0
    // 发送信号其实调用的就是这个信号函数
    // 信号函数由moc通过QMetaObject::activate实现
    // 第一个参数为当前对象指针this
    // 第二个参数为上面介绍的staticMetaObject
    // 第三个参数为从0开始的信号索引
    // 第四个参数为空指针NULL
    void MainWindow::mousePressed()
    {
        QMetaObject::activate(this, &staticMetaObject, 0, Q_NULLPTR);
    }

    示例中的connect函数用到了SIGNAL与SLOT宏,它们分debug和非debug两个版本,非debug版本就是在参数前面添加一个数字,信号为2,槽为1,源码如下。

    // qglobal.h
    /* These two macros makes it possible to turn the builtin line expander into a
     * string literal. */
    #define QT_STRINGIFY2(x) #x
    #define QT_STRINGIFY(x) QT_STRINGIFY2(x)
    // qobjectdefs.h
    Q_CORE_EXPORT const char *qFlagLocation(const char *method);
    
    #ifndef QT_NO_META_MACROS
    #ifndef QT_NO_DEBUG
    # define QLOCATION "\0" __FILE__ ":" QT_STRINGIFY(__LINE__)
    # ifndef QT_NO_KEYWORDS
    #  define METHOD(a)   qFlagLocation("0"#a QLOCATION)
    # endif
    # define SLOT(a)     qFlagLocation("1"#a QLOCATION)
    # define SIGNAL(a)   qFlagLocation("2"#a QLOCATION)
    #else
    # ifndef QT_NO_KEYWORDS
    #  define METHOD(a)   "0"#a
    # endif
    # define SLOT(a)     "1"#a
    # define SIGNAL(a)   "2"#a
    #endif
    
    #define QMETHOD_CODE  0                        // member type codes
    #define QSLOT_CODE    1
    #define QSIGNAL_CODE  2
    #endif // QT_NO_META_MACROS

    5、connect

    使用信号前,首先要进行connect,示例中的connect代码如下。

    connect(this, SIGNAL(mousePressed()), this, SLOT(onMousePressed()));

    connect有多个重载函数,下面以示例中的用法为例展开说明,源码如下。

    QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,
                                         const QObject *receiver, const char *method,
                                         Qt::ConnectionType type)
    {
        // 先判断函数参数是否有效
        if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
            qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
                     sender ? sender->metaObject()->className() : "(null)",
                     (signal && *signal) ? signal+1 : "(null)",
                     receiver ? receiver->metaObject()->className() : "(null)",
                     (method && *method) ? method+1 : "(null)");
            return QMetaObject::Connection(0);
        }
        QByteArray tmp_signal_name;
    
        // 检查信号对应的宏SIGNAL是否正确使用
        // SIGNAL在信号前面添加了数字2
        // check_signal_macro通过这个数字2进行检查
        // 是否正确使用了SIGNAL
        if (!check_signal_macro(sender, signal, "connect", "bind"))
            return QMetaObject::Connection(0);
        const QMetaObject *smeta = sender->metaObject();
        const char *signal_arg = signal;
        ++signal; // 跳过SIGNAL宏中的数字2
        QArgumentTypeArray signalTypes;
        Q_ASSERT(QMetaObjectPrivate::get(smeta)->revision >= 7); // moc设置了revision为7
        // 提取信号名signalName和参数列表signalTypes
        // decodeMethodSignature函数使用了strchr函数定位左、右括号在signal字符串中的位置
        QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
        // 提取信号索引signal_index
        // 从当前类到父类查找signalName对应的索引
        // 失败时返回-1
        int signal_index = QMetaObjectPrivate::indexOfSignalRelative(
                &smeta, signalName, signalTypes.size(), signalTypes.constData());
        if (signal_index < 0) {
            // check for normalized signatures
            tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
            signal = tmp_signal_name.constData() + 1;
    
            signalTypes.clear();
            signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
            smeta = sender->metaObject();
            signal_index = QMetaObjectPrivate::indexOfSignalRelative(
                    &smeta, signalName, signalTypes.size(), signalTypes.constData());
        }
        if (signal_index < 0) {
            err_method_notfound(sender, signal_arg, "connect");
            err_info_about_objects("connect", sender, receiver);
            return QMetaObject::Connection(0);
        }
        signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);
        signal_index += QMetaObjectPrivate::signalOffset(smeta);
    
        // 同理下面获取槽的名字和索引
       // 因为信号可以连接到槽和另外一个信号
       // 所以对槽进行处理时还要判断是否为信号
        QByteArray tmp_method_name;
        int membcode = extract_code(method);
    
        if (!check_method_code(membcode, receiver, method, "connect"))
            return QMetaObject::Connection(0);
        const char *method_arg = method;
        ++method; // skip code
    
        QByteArray methodName;
        QArgumentTypeArray methodTypes;
        const QMetaObject *rmeta = receiver->metaObject();
        int method_index_relative = -1;
        Q_ASSERT(QMetaObjectPrivate::get(rmeta)->revision >= 7);
        switch (membcode) {
        case QSLOT_CODE:
            method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
                    &rmeta, methodName, methodTypes.size(), methodTypes.constData());
            break;
        case QSIGNAL_CODE:
            method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
                    &rmeta, methodName, methodTypes.size(), methodTypes.constData());
            break;
        }
        if (method_index_relative < 0) {
            // check for normalized methods
            tmp_method_name = QMetaObject::normalizedSignature(method);
            method = tmp_method_name.constData();
    
            methodTypes.clear();
            methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
            // rmeta may have been modified above
            rmeta = receiver->metaObject();
            switch (membcode) {
            case QSLOT_CODE:
                method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
                        &rmeta, methodName, methodTypes.size(), methodTypes.constData());
                break;
            case QSIGNAL_CODE:
                method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
                        &rmeta, methodName, methodTypes.size(), methodTypes.constData());
                break;
            }
        }
    
        if (method_index_relative < 0) {
            err_method_notfound(receiver, method_arg, "connect");
            err_info_about_objects("connect", sender, receiver);
            return QMetaObject::Connection(0);
        }
        // 检查信号与槽的参数列表是否一致
        if (!QMetaObjectPrivate::checkConnectArgs(signalTypes.size(), signalTypes.constData(),
                                                  methodTypes.size(), methodTypes.constData())) {
            qWarning("QObject::connect: Incompatible sender/receiver arguments"
                     "\n        %s::%s --> %s::%s",
                     sender->metaObject()->className(), signal,
                     receiver->metaObject()->className(), method);
            return QMetaObject::Connection(0);
        }
        // 对connect的类型进行处理
        int *types = 0;
        if ((type == Qt::QueuedConnection)
                && !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) {
            return QMetaObject::Connection(0);
        }
        // 最后通过QMetaObjectPrivate::connect进行真正的connect
    #ifndef QT_NO_DEBUG
        QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
        QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset());
        check_and_warn_compat(smeta, smethod, rmeta, rmethod);
    #endif
        QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
            sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));
        return handle;
    }

    下面是QMetaObjectPrivate::connect的源码实现。

    QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,
                                     int signal_index, const QMetaObject *smeta,
                                     const QObject *receiver, int method_index,
                                     const QMetaObject *rmeta, int type, int *types)
    {
        // sender和receiver去const
        QObject *s = const_cast<QObject *>(sender);
        QObject *r = const_cast<QObject *>(receiver);
    
        // 获取receiver中method的偏移量
       // 因为其method_index是个相对值
        int method_offset = rmeta ? rmeta->methodOffset() : 0;
        Q_ASSERT(!rmeta || QMetaObjectPrivate::get(rmeta)->revision >= 6);
        QObjectPrivate::StaticMetaCallFunction callFunction =
            rmeta ? rmeta->d.static_metacall : 0;
    
        // 对sender和receiver上锁(mutex pool)
        QOrderedMutexLocker locker(signalSlotLock(sender),
                                   signalSlotLock(receiver));
    
        // type为Qt::UniqueConnection时作特殊处理
        // 确保connect的唯一性
        if (type & Qt::UniqueConnection) {
            QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;
            if (connectionLists && connectionLists->count() > signal_index) {
                const QObjectPrivate::Connection *c2 =
                    (*connectionLists)[signal_index].first;
    
                int method_index_absolute = method_index + method_offset;
    
                while (c2) {
                    if (!c2->isSlotObject && c2->receiver == receiver && c2->method() == method_index_absolute)
                        return 0;
                    c2 = c2->nextConnectionList;
                }
            }
            type &= Qt::UniqueConnection - 1;
        }
        // 最后是真正的connect对象QObjectPrivate::Connection实例化
        // 存储了所有的connect信息
        // addConnection最终保存了这个connect操作
        QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
        c->sender = s;
        c->signal_index = signal_index;
        c->receiver = r;
        c->method_relative = method_index;
        c->method_offset = method_offset;
        c->connectionType = type;
        c->isSlotObject = false;
        c->argumentTypes.store(types);
        c->nextConnectionList = 0;
        c->callFunction = callFunction;
    
        QObjectPrivate::get(s)->addConnection(signal_index, c.data());
        // 解锁
        locker.unlock();
        // connect成功后还会调用一次connectNotify函数
        // connectNotify是个虚函数
        // 我们可以重写connectNotify在connenct成功后进行额外的相关操作
        QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
        if (smethod.isValid())
            s->connectNotify(smethod);
    
        return c.take();
    }

    6、activate

    发送信号时,实际上是调用了QMetaObject::activate函数,这是Qt用于内部实现的函数,开发者无法直接使用这个函数。

        // internal index-based signal activation
        static void activate(QObject *sender, int signal_index, void **argv);
        static void activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv);
        static void activate(QObject *sender, int signal_offset, int local_signal_index, void **argv);

    activate最终是通过上面的最后一个函数实现的,参数分别为信号发送者对象指针、信号在元对象数据结构中的偏移量及信号索引、信号参数,可以想象,这个函数就是在前面添加的connect列表中查找并调用这个信号连接的槽或者信号,源码实现如下。

    void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
    {
        // 信号在元对象数据结构中的实际索引
        int signal_index = signalOffset + local_signal_index;
        // 判断信号是否已经connect
        // 判断是否注册了信号监听回调函数(用于QTest)
        if (!sender->d_func()->isSignalConnected(signal_index)
            && !qt_signal_spy_callback_set.signal_begin_callback
            && !qt_signal_spy_callback_set.signal_end_callback) {
            return; // nothing connected to these signals, and no spy
        }
        // 判断信号是否被block
        if (sender->d_func()->blockSig)
            return;
        // 用于QTest
        if (sender->d_func()->declarativeData && QAbstractDeclarativeData::signalEmitted)
            QAbstractDeclarativeData::signalEmitted(sender->d_func()->declarativeData, sender,
                                                    signal_index, argv);
        // 用于QTest begin
        void *empty_argv[] = { 0 };
        if (qt_signal_spy_callback_set.signal_begin_callback != 0) {
            qt_signal_spy_callback_set.signal_begin_callback(sender, signal_index,
                                                             argv ? argv : empty_argv);
        }
        // HANDLE句柄即当前的线程id
        // unix平台上通过pthread_self获取
        Qt::HANDLE currentThreadId = QThread::currentThreadId();
    
        {
        // 上锁(多线程、异步)
        QMutexLocker locker(signalSlotLock(sender));
        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; }
        };
        ConnectionListsRef connectionLists = sender->d_func()->connectionLists;
        // connectionLists为空时unlock后直接return
        if (!connectionLists.connectionLists) {
            locker.unlock();
            // 用于QTest end
            if (qt_signal_spy_callback_set.signal_end_callback != 0)
                qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);
            return;
        }
        // 获取connect列表
        const QObjectPrivate::ConnectionList *list;
        if (signal_index < connectionLists->count())
            list = &connectionLists->at(signal_index);
        else
            list = &connectionLists->allsignals;
    
        do {
            QObjectPrivate::Connection *c = list->first;
            // 循环取得一个非空的Connection
            if (!c) continue;
            // We need to check against last here to ensure that signals added
            // during the signal emission are not emitted in this emission.
            QObjectPrivate::Connection *last = list->last;
    
            do {
                // 查找有效的receiver
                if (!c->receiver)
                    continue;
    
                QObject * const receiver = c->receiver;
                // 判断当前线程与receiver线程是否一致
                const bool receiverInSameThread = currentThreadId == receiver->d_func()->threadData->threadId;
    
                // 根据connect类型及receiverInSameThread进行不同的处理
                // 立即执行queued_activate或者放入消息队列postEvent等待后续处理
                if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                    || (c->connectionType == Qt::QueuedConnection)) {
                    queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker);
                    continue;
    #ifndef QT_NO_THREAD
                } else if (c->connectionType == Qt::BlockingQueuedConnection) {
                    locker.unlock();
                    if (receiverInSameThread) {
                        qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: "
                        "Sender is %s(%p), receiver is %s(%p)",
                        sender->metaObject()->className(), sender,
                        receiver->metaObject()->className(), receiver);
                    }
                    // 多线程时势必要用到同步机制(锁、信号量)
                    QSemaphore semaphore;
                    QMetaCallEvent *ev = c->isSlotObject ?
                        new QMetaCallEvent(c->slotObj, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore) :
                        new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore);
                    QCoreApplication::postEvent(receiver, ev);
                    semaphore.acquire();
                    locker.relock();
                    continue;
    #endif
                }
    
                QConnectionSenderSwitcher sw;
    
                if (receiverInSameThread) {
                    sw.switchSender(receiver, sender, signal_index);
                }
                // 下面通过三种方法去调用信号连接的槽
                const QObjectPrivate::StaticMetaCallFunction callFunction = c->callFunction;
                const int method_relative = c->method_relative;
                if (c->isSlotObject) {
                    c->slotObj->ref();
                    QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);
                    locker.unlock();
                    // 方法一 通过call调用receiver中的函数
                    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();
                } else if (callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
                    //we compare the vtable to make sure we are not in the destructor of the object.
                    locker.unlock();
                    const int methodIndex = c->method();
                    if (qt_signal_spy_callback_set.slot_begin_callback != 0)
                        qt_signal_spy_callback_set.slot_begin_callback(receiver, methodIndex, argv ? argv : empty_argv);
                    // 方法二 callFunction即moc实现的qt_static_metacall
                    callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);
    
                    if (qt_signal_spy_callback_set.slot_end_callback != 0)
                        qt_signal_spy_callback_set.slot_end_callback(receiver, methodIndex);
                    locker.relock();
                } else {
                    const int method = method_relative + c->method_offset;
                    locker.unlock();
    
                    if (qt_signal_spy_callback_set.slot_begin_callback != 0) {
                        qt_signal_spy_callback_set.slot_begin_callback(receiver,
                                                                    method,
                                                                    argv ? argv : empty_argv);
                    }
                    // 方法三 通过metacall调用moc实现的qt_matacall
                    metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
    
                    if (qt_signal_spy_callback_set.slot_end_callback != 0)
                        qt_signal_spy_callback_set.slot_end_callback(receiver, method);
    
                    locker.relock();
                }
                // orphaned为true时说明connectionLists的所属QObject已经销毁
                // 尽管connectionLists是inUse但没有什么意思
                // 所以跳出循环
                if (connectionLists->orphaned)
                    break;
            } while (c != last && (c = c->nextConnectionList) != 0);
    
            if (connectionLists->orphaned)
                break;
        } while (list != &connectionLists->allsignals &&
            //start over for all signals;
            ((list = &connectionLists->allsignals), true));
    
        }
        // 用于QTest(end)
        if (qt_signal_spy_callback_set.signal_end_callback != 0)
            qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);
    }

    7、over

    简单来说,信号与槽的关键就是Qt的元对象系统,通过moc编译隐藏了具体的实现细节,这些内容可以在moc_xxx.cpp中查看。

    展开全文
  • QT 信号机制

    2013-01-04 03:03:59
    详解QT 信号机制 (上篇) QT 信号机制 是本文要介绍的内容,Qt用预编译器和宏来保证强大的跨平台能力,信号机制则是其中最精妙之处。本文分析了几种常见的信号处理机制,然后详细介绍了Qt的Signal/Slot机制...

    详解QT 信号机制 (上篇)


    QT 信号机制 是本文要介绍的内容,Qt用预编译器和宏来保证强大的跨平台能力,信号机制则是其中最精妙之处。本文分析了几种常见的信号处理机制,然后详细介绍了Qt的Signal/Slot机制
     
    首先要说明,这里所说的信号不是Unix中进程间通信的信号。这里的信号更多地与图形界面的输入输出联系在一起(当然也可以是不可见的操作)。自从计算机程序从字符界面转为图形界面,用户的输入一下子变得繁杂和丰富起来,不同的输入位置、不同的输入设备、不同的焦点位置、不同的输入值组合起来构成了许许多多的信号。一下子,这个世界变得五彩缤纷。当前的三大主流操作系统??Windows、Unix和MAC都提供了令人赏心悦目的图形界面。

    虽然它们出自不同公司,自身还有很多分支,但是在图形操作与管理上还是大致类同的:都有桌面、有图标,有大大小小规则或不规则的窗口,窗口上有标题、边框、菜单以及按钮等各种控件,用户可以用键盘在当前焦点输入内容,可以用鼠标点击任意的窗口和控件。就能动性来说,是由用户主导程序下一步作何操作,而不象字符时代那样由程序来主导用户。这也就是所谓的“事件驱动”。在一个事件驱动的系统中,不论是Windows,还是Unix,都脱离不了以下的处理框架:

    当某个应用程序收到操作系统发送的事件时,它就要判断这个事件该由谁处理。处理过程本身又可能引起新的事件发生,这就要告诉操作系统我发出了什么信号。如此这般循环往复,青山之水常流。那么,一个具体的信号究竟是如何触发与它对应的函数呢?绝大部分的系统都是采用了回调的机制,所谓“回调”其实就是指向某一个函数的指针。

    在C语言中函数名其实也是一个指针,因此回调其实是一个指向指针的指针。在不同的开发框架或开发包中,对于回调的实现有着一些细微的差别。初接触Qt时,我一直在想它是如何处理各种平台的信号调用。虽然C语言本身是平台无关的,但具体到某一个操作系统、某一个开发包,信号机制会有些不同。

    而信号是面向对象的开发环境中一个很重要的环节,如果要设计一个类库或程序框架,就必须很好地考虑不同平台间的差异。接触了Qt之后,感觉Qt选择了一条颇具特色的处理途径??Signals/Slot,中文名暂定为“信号/反应槽”。在Qt的内部设计中,通过信号/反应槽(signals/slot)的使用对回调进行了很好的封装。为了更好地了解该机制我们先看一下其他几种常用的信号相关程序。

    1.Win32

    Win32的程序总是从WinMain开始执行。在WinMain的代码中,主要功能一般有三个:一是注册窗口类,二是在屏幕上显示窗口,三是实现消息环。消息环的作用就是从应用程序队列中取出操作系统放入的消息,从而实现用户和程序之间的交互(也包括象定时器之类的非用户输入的消息)。应用程序不定期地在消息环中等待消息的到来。如下所示:// 消息环

     
    1. while(GetMessage(&msg, NULL, 0, 0))  
    2. {  
    3. TranslateMessage(&msg);  
    4. DispatchMessage(&msg);  

    这一段程序包括了形成一个标准消息环的三个基本API:GetM essage()、TranslateMessage()和ispatchMessage()

    ,采用加速键和非模式对话框时将相应改变消息环的结构。在Windows中,GetMessage()是多任务的核心。在应用程序的消息队列中出现一条消息之前,该函数并不返回任何东西。GetMessage()的等待阻塞了当前进程,因而为正在运行的其他应用程序提供了检查私有消息环的机会。出现一条消息后,GetMessage()将取出该消息,并将信息存储在一个MSG数据结构中。对于每一条迫使退出消息环、进程终止的消息(WM_QUIT除外),GetMessage()返回TRUE。

    通常在消息环后面跟一个返回语句,迫使WinMain()返回系统。紧跟着GetMessage()的TranslateMessage()对msg进行处理并修改该数据块的内容。DispatchMessage()负责查找应调用哪一个窗口过程,这种选择是根据msg中hwnd所标识的窗口进行决策。窗口过程对消息进行处理, 完毕后即返回到消息环, 再次执行GetMessage()。如下图所示:

    为了对所关心的消息做出处理,窗口在创建时一定要提供一个消息回调函数,不管该创建过程是显式调用还是其他API函数隐式生成。用户在该回调函数中要对每一个关心的消息做出判断与处理,从C语言的观点来看,一个窗口过程(回调函数)就是这样一个函数:接受四个参数,返回一个LRESULT值,一个switch语句在过程内占用了大量的代码以完成各个行为动作。

    2.MFC

    虽然直接用Win32 API开发的程序运行效率高、条理分明,但开发起来却较为复杂,维护工时也耗用较多,因此现在Windows环境中大部分用C++开发的应用程序使用了微软提供的MFC类库。它是面向对象设计的,虽然乍一看其编程风格与Win32迥然不同,但那是高度封装的结果,其内部的实现与Win32没有区别。

    MFC的一个主导设计思想就是程序框架下(CFrameWnd)的视图/文档模型,同时定义了许多宏来简化编程,其消息的传递也与宏息息相关(有关MFC的解剖可看侯捷先生的《深入浅出MFC》第二版)。通过使用这些宏,应用程序自身将维护这一张可能为数不菲的消息映射表。对于程序员来说,只需要点击鼠标就可完成以上的工作,开发效率有了很大的提高。

    3.Linux

    Linux(包括其他的Unix)和Windows的一个很大不同点在于其图形界面的管理是与内核分开的,负责图形操作(还包括键盘、鼠标等事件捕获)的模块是X Window。请注意,此处的“Window”与微软的Windows毫无亲戚关系。X Window包括三大部分:服务端(XServer)、客户端(X Client)和协议(X protocol),示意图如下:我们平时在Linux下开发的有图形界面的程序一般就是X Window中的客户端程序,相对应的库就是X Lib。

    X Lib是X Window中最低层的接口库,相当于微软Windows中的 API。这个库封装了对X protocol的存取,提供了超过610个函数。由于X protocol可以在网络上传播,因此X Window中服务器端和客户端可以不在一台机器上,这一点和微软Windows有着很大的区别。对比X Lib与Win32 API的处理方式,可以发现虽然两者框架不一、风格不一,但在流程处理上都有异曲同工之妙。

    4.Qt

    Qt中的类库有接近一半是从基类QObject上继承下来,信号与反应槽(signals/slot)机制就是用来在QObject类或其子类间通讯的方法。作为一种通用的处理机制,信号与反应槽非常灵活,可以携带任意数量的参数,参数的类型也由用户自定。

    同时其本身也是类型安全的,任何一个从QObject或其子类继承的用户类都可以使用信号与反应槽。信号的作用如同Windows系统中的消息。在Qt中,对于发出信号的对象来说,它并不知道是谁接收了这个信号。这样的设计可能在某些地方会有些不便,但却杜绝了紧耦合,于总体设计有利。反应槽是用来接收信号的, 但它实际上也是普通的函数,程序员可以象调用普通函数一样来调用反应槽。与信号类似的是,反应槽的拥有者也不知道是谁向它发出了信号。

    在程序设计过程中,多个信号可以连接至一个反应槽,类似的,一个信号也可以连接至多个反应槽,甚至一个信号可以连接至另一个信号。在Windows中,如果我们需要多个菜单都激发一个函数,一般是先写一个共用函数,然后在每个菜单的事件中调用此函数。在Qt中如果要实现同样的功能,就可以把实现部分写在一个菜单中,然后把其他菜单与这个菜单级联起来。

    虽然信号/反应槽机制有很多优点,使用也很方便,但它也不是没有缺点。最大的缺点在于要稍微牺牲一点性能。根据Trolltech公司的自测,在CPU为Intel PentiumII 500 Mhz的PC机上,对于一个信号对应一个反应槽的连接来说,一秒钟可以调用两百万次;对于一个信号对应两个反应槽的连接来说,一秒钟可以调用一百二十万次。

    这个速度是不经过连接而直接进行回调的速度的十分之一。请注意这里的十分之一速度比是调用速度的比较,而不是一个完整函数执行时间的比较。事实上一般情况下一个函数的总执行时间大部分是在执行部分,只有小部分是在调用部分,因些这个速度是可以接受的。这就象面向对象的编程和早些年的结构化编程相比一样:程序的执行效率并没有提高,反而是有所下降的,但现在大家都在用面向对象的方法编写程序。用一部分执行效率换回开发效率与维护效率是值得的,况且现在已是P4为主流的时代。我们先来看一个简单的样例:

     
    1. class Demo : public QObject  
    2. {  
    3. Q_OBJECT  
    4. public:  
    5. Demo();  
    6. int value() const { return val; };  
    7. public slots:  
    8. void setValue( int );  
    9. signals:  
    10. void valueChanged( int );  
    11. private:  
    12. int val;  
    13. }; 

    由样例可看到,类的定义中有两个关键字slots和signals,还有一个宏Q_OBJECT。在Qt的程序中如果使用了信号与反应槽就必须在类的定义中声明这个宏,不过如果你声明了该宏但在程序中并没有信号与反应槽,对程序也不会有任何影响,所以建议大家在用Qt写程序时不妨都把这个宏加上。使用slots定义的就是信号的功能实现,即反应槽,例如:

     
    1. void Demo::setValue( int v )  
    2. {  
    3. if ( v != val ) {  
    4. vval = v;  
    5. emit valueChanged(v);  
    6. }  

    这段程序表明当setValue执行时它将释放出valueChanged这个信号。以下程序示范了不同对象间信号与反应槽的连接。

     
    1. Demo a, b;  
    2. connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));  
    3. b.setValue( 11 );  
    4. a.setValue( 7Array );  
    5. b.value(); // b的值将是7Array而不是原先设的11 

    在以上程序中,一旦信号与反应槽连接,当执行a.setValue(7Array)时就会释放出一个valueChanged(int)的信号,对象b将会收到这个信号并触发setValue(int)这个函数。当b在执行setValue(int)这个函数时,它也将释放valueChanged(int)这个信号,当然b 的信号无人接收,因此就什么也没干。示意图如下:请注意,在样例中我们仅当输入变量v不等于val时才释放信号,因此就算对象

    a与b进行了交叉连接也不会导致死循环的发生。由于在样例中使用了Qt特有的关键字和宏,而Qt本身并不包括C++的编译器,因此如果用流行的编译程序(如Windows下的Visual C++或Linux下的gcc)是不能直接编译这段代码的,必须用Qt的中间编译工具moc.exe把该段代码转换为无专用关键字和宏的C++代码才能为这些编译程序所解析、编译与链接。

    以上代码中信号与反应槽的定义是在类中实现的。那么,非类成员的函数,比如说一个全局函数可不可以也这样做呢?答案是不行,只有是自身定义了信号的类或其子类才可以发出该种信号。一个对象的不同信号可以连接至不同的对象。

    当一个信号被释放时,与之连接的反应槽将被立刻执行,就象是在程序中直接调用该函数一样。信号的释放过程是阻塞的,这意味着只有当反应槽执行完毕后该信号释放过程才返回。如果一个信号与多个反应槽连接,则这些反应槽将被顺序执行,排序过程则是任意的。因此如果程序中对这些反应槽的先后执行次序有严格要求的话,应特别注意。使用信号时还应注意:信号的定义过程是在类的定义过程即头文件中实现的。

    为了中间编译工具moc的正常运行,不要在源文件(.cpp)中定义信号,同时信号本身不应返回任何数据类型,即是空值(void)。如果你要设计一个通用的类或控件,则在信号或反应槽的参数中应尽可能使用常规数据以增加通用性。如上例代码中valueChanged的参数为int型,如果它使用了特殊类型如QRangeControl::Range,那么这种信号只能与RangeControl中的反应槽连接。如前所述,反应槽也是常规函数,与未定义slots的用户函数在执行上没有任何区别。

    但在程序中不可把信号与常规函数连接在一起,否则信号的释放不会引起对应函数的执行。要命的是中间编译程序moc并不会对此种情况报错,C++编译程序更不会报错。初学者比较容易忽略这一点,往往是程序编好了没有错误,逻辑上也正确,但运行时就是不按自己的意愿出现结果,这时候应检查一下是不是这方面的疏忽。Qt的设计者之所以要这样做估计是为了信号与反应槽之间匹配的严格性。既然反应槽与常规函数在执行时没有什么区别,因此它也可以定义成公共反应槽(public slots)、保护反应槽(protected slots)和私有反应槽(private slots)。如果需要,我们也可以把反应槽定义成虚函数以便子类进行不同的实现,这一点是非常有用的。

    只讨论一下信号与反应槽的使用好象还不过瘾,既然Qt的X11 Free版提供了源代码,我们就进去看一下在QObject中connect的实现。由于Qt是一个跨平台的开发库,为了与不同平台上的编译器配合,它定义了一个中间类QMetaObject,该类的作用是存放有关信号/反应槽以及对象自身的信息。这个类是Qt内部使用的,用户不应去使用它。


    详解QT 信号机制 (下篇)

    继续 详解QT 信号机制 (上篇) 的内容接续介绍,本节介绍的是详解QT 信号机制 (下篇),以下是QMetaObject的定义(为了浏览方便,删除了一部分次要代码):

     
    1. class Q_EXPORT QMetaObject  
    2. {  
    3. public:  
    4. QMetaObject( const char * const class_name, QMetaObject *superclass,  
    5. const QMetaData * const slot_data, int n_slots,  
    6. const QMetaData * const signal_data, int n_signals);  
    7. virtual ~QMetaObject();  
    8. int numSlots( bool super = FALSE ) const; /* 反应槽的数量 */  
    9. int numSignals( bool super = FALSE ) const; /* 信号的数量 */  
    10. int findSlot( const char *, bool super = FALSE ) const;  
    11. /* 根据反应槽的名称找到其在列表中的索引 */  
    12. int findSignal( const char *, bool super = FALSE ) const;  
    13. /* 根据信号的名称找到其在列表中的索引 */  
    14. const QMetaData *slot( int index, bool super = FALSE ) const;  
    15. /* 根据索引取得反应槽的数据 */  
    16. const QMetaData *signal( int index, bool super = FALSE ) const;  
    17. /* 根据索引取得信号的数据 */  
    18. QStrList slotNames( bool super = FALSE ) const;  
    19. /* 取得反应槽列表 */  
    20. QStrList signalNames( bool super = FALSE ) const;  
    21. /* 取得信号列表 */  
    22. int slotOffset() const;  
    23. int signalOffset() const;  
    24. static QMetaObject *metaObject( const char *class_name );  
    25. private:  
    26. QMemberDict *init( const QMetaData *, int );  
    27. const QMetaData *slotData; /* 反应槽数据指针 */  
    28. QMemberDict *slotDict; /* 反应槽数据字典指针 */  
    29. const QMetaData *signalData; /* 信号数据指针*/  
    30. QMemberDict *signalDict; /* 信号数据字典指针*/  
    31. int signaloffset;  
    32. int slotoffset;  
    33. }; 

    再看一下QObject中connect的实现。剥去粗枝,函数中便露出一个更细化的函数:connectInternal,它又做了哪些工作呢?让我们看一下:

     
    1. void QObject::connectInternal( const QObject *sender, int signal_index,  
    2. const QObject *receiver,  
    3. int membcode, int member_index )  
    4. {  
    5. QObject *s = (QObject*)sender;  
    6. QObject *r = (QObject*)receiver;  
    7. if ( !s->connections ) {  
    8. /* 如果某个对象有信号或反应槽但没有建立相互连接是不会建立连接列表的,这样可减少一些无谓的资源消耗 */  
    9. s->connections = new QSignalVec( 7 );  
    10. s->connections->setAutoDelete( TRUE );  
    11. /* 无连接时,连接列表将被自动删除 */  
    12. }  
    13. QConnectionList *clist = s->connections->at( signal_index );  
    14. if ( !clist ) {  
    15. /* 建立与信号源对象中某一个信号所对应的接收对象的列表 */  
    16. clist = new QConnectionList;  
    17. clist->setAutoDelete( TRUE );  
    18. s->connections->insert( signal_index, clist );  
    19. }  
    20. QMetaObject *rrmeta = r->metaObject();  
    21. switch ( membcode ) {  
    22. /* 取得信号或反应槽的数据指针 */  
    23. case QSLOT_CODE:  
    24. rm = rmeta->slot( member_index, TRUE );  
    25. break;  
    26. case QSIGNAL_CODE:  
    27. rm = rmeta->signal( member_index, TRUE );  
    28. break;  
    29. }  
    30. QConnection *c = new QConnection( r, member_index,  
    31. rm ? rm->name : "qt_invoke", membcode );  
    32. /* 创建一个新的信号/反应槽连接 */  
    33. clist->append( c ); /* 信号源端加入这一对连接 */  
    34. if ( !r->senderObjects ) {  
    35. /* 类似于信号源端,反应槽端的连接列表也是动态创建的 */  
    36. r->senderObjects = new QObjectList;  
    37. }  
    38. r->senderObjects->append( s ); /* 反应槽端加入这一对连接 */  

    到此,信号与反应槽的连接已建立完毕,那么信号产生时又是如何触发反应槽的呢?从QObject的定义中可以看出其有多个activate_signal的成员函数,这些函数都是protected的,也即只有其自身或子类才可以使用。看一下它的实现:

     
    1. void QObject::activate_signal( QConnectionList *clist, QUObject *o )  
    2. {  
    3. if ( !clist ) /* 有效性检查 */  
    4. return;  
    5. QObject *object;  
    6. QConnection *c;  
    7. if ( clist->count() == 1 ) {  
    8. /* 对某一个对象的一个具体信号来说,一般只有一种反应槽与之相连,这样事先判断一下可以加快处理速度 */  
    9. c = clist->first();  
    10. object = c->object();  
    11. sigSender = this;  
    12. if ( c->memberType() == QSIGNAL_CODE )  
    13. object->qt_emit( c->member(), o ); /* 信号级连 */  
    14. else  
    15. object->qt_invoke( c->member(), o );/* 调用反应槽函数 */  
    16. } else {  
    17. QConnectionListIt it(*clist);  
    18. while ( (c=it.current()) ) { /* 有多个连接时,逐一扫描 */  
    19. ++it;  
    20. object = c->object();  
    21. sigSender = this;  
    22. if ( c->memberType() == QSIGNAL_CODE )  
    23. object->qt_emit( c->member(), o ); /* 信号级连 */  
    24. else  
    25. object->qt_invoke( c->member(), o ); /* 调用反应槽函数 */  
    26. }  
    27. }  

    至此我们已经可以基本了解Qt中信号/反应槽的流程。我们再看一下Qt为此而新增的语法:三个关键字:slots、signals和emit,三个宏:SLOT()、SIGNAL()和Q_OBJECT。在头文件qobjectdefs.h中,我们可以看到这些新增语法的定义如下:

     
    1. #define slots // slots: in class  
    2. #define signals protected // signals: in class  
    3. #define emit // emit signal  
    4. #define SLOT(a) "1"#a  
    5. #define SIGNAL(a) "2"#a 

    由此可知其实三个关键字没有做什么事情,而SLOT()和SIGNAL()宏也只是在字符串前面简单地加上单个字符,以便程序仅从名称就可以分辨谁是信号、谁是反应槽。中间编译程序moc.exe则可以根据这些关键字和宏对相应的函数进行“翻译”,以便在C++编译器中编译。剩下一个宏Q_OBJECT比较复杂,它的定义如下:

     
    1. #define Q_OBJECT \  
    2. publi \  
    3. virtual QMetaObject *metaObject() const { \  
    4. return staticMetaObject(); \  
    5. }  
    6. \  
    7. virtual const char *className() const; \  
    8. virtual void* qt_cast( const char* ); \  
    9. virtual bool qt_invoke( int, QUObject* ); \  
    10. virtual bool qt_emit( int, QUObject* ); \  
    11. QT_PROP_FUNCTIONS  
    12. \  
    13. static QMetaObject* staticMetaObject(); \  
    14. QObject* qObject() { return (QObject*)this; } \  
    15. QT_TR_FUNCTIONS  
    16. \  
    17. private: \  
    18. static QMetaObject *metaObj; 

    从定义中可以看出该宏的作用有两个:一是对与自己相关的QMetaObject中间类操作进行声明,另一个是对信号的释放操作和反应槽的激活操作进行声明。当moc.exe对头文件进行预编译之后,将会产生一个可供C++编译器编译的源文件。以上述的Demo类为例,假设它的代码文件分别为d e m o . h和d e m o . c p p ,预编译后将产生

    moc_demo.cpp,其主要内容如下:

     
    1. QMetaObject *Demo::metaObj = 0;  
    2. void Demo::initMetaObject()  
    3. {  
    4. if ( metaObj )  
    5. return;  
    6. if ( strcmp(QObject::className(), "QObject") != 0 )  
    7. badSuperclassWarning("Demo","QObject");  
    8. (void) staticMetaObject();  
    9. }  
    10. QMetaObject* Demo::staticMetaObject()  
    11. {  
    12. if ( metaObj )  
    13. return metaObj;  
    14. (void) QObject::staticMetaObject();  
    15. typedef void(Demo::*m1_t0)(int);  
    16. m1_t0 v1_0 = Q_AMPERSAND Demo::setValue; /* 定位反应槽的入口 */  
    17. QMetaData *slot_tbl = QMetaObject::new_metadata(1);  
    18. /* 新建一个反应槽数据 */  
    19. QMetaData::Access *slot_tbl_access = QMetaObject::new_metaaccess(1);  
    20. slot_tbl[0].name = "setValue(int)"; /* 反应槽名称 */  
    21. slot_tbl[0].ptr = *((QMember*)&v1_0);  
    22. /* 通过反应槽名称可以找到反应槽的入口指针 */  
    23. slot_tbl_access[0] = QMetaData::Public; /* 权限类型 */  
    24. typedef void(Demo::*m2_t0)(int);  
    25. m2_t0 v2_0 = Q_AMPERSAND Demo::valueChanged; /* 定位信号的入口 */  
    26. QMetaData *signal_tbl = QMetaObject::new_metadata(1); /* 新建信号数据 */  
    27. signal_tbl[0].name = "valueChanged(int)"; /* 信号名称 */  
    28. signal_tbl[0].ptr = *((QMember*)&v2_0);  
    29. /* 通过信号名称可以找到信号的入口指针 */  
    30. metaObj = QMetaObject::new_metaobject(  
    31. /* 创建一个与demo类相关的QMetaObject对象 */  
    32. "Demo", "QObject",  
    33. slot_tbl, 1,  
    34. signal_tbl, 1,  
    35. 0, 0 );  
    36. metaObj->set_slot_access( slot_tbl_access ); /* 设置权限 */  
    37. return metaObj;  
    38. }  
    39. // 有信号时即激活对应的反应槽或另一个信号  
    40. void Demo::valueChanged( int t0 )  
    41. {  
    42. activate_signal( "valueChanged(int)", t0 );  

    该文件中既没有Qt特有的关键字,也没有特殊的宏定义,完全符合普通的C++语法,因此可以顺利编译和链接。


    展开全文
  • Qt 信号与槽

    千次阅读 2018-02-10 16:00:07
    信号和槽用于两个对象之间的通信,信号和槽机制是Qt的核心特征。 为了实现对象间的通信,一些工具包中使用了回调(callback)机制,而在Qt中,使用了信号和槽来进行对象间的通信。 信号和槽的关联,可以是一个信号...
  • QT信号槽多次连接引起的错误修改

    千次阅读 2016-05-16 22:25:02
    1. 问题描述:在有关QT的编程中,遇到了这样的问题,当由于对QT信号槽连接机制的不理解,导致了对同一个信号槽进行了两次或以上的连接,结果在之后的代码调试中便发生了与自己期望不同的结果。通过在网络上查找相关...
  • Qt信号与槽

    2017-12-17 15:02:34
    Qt中理解信号与槽机制非常重要,在Qt中进行了一段时间的开发之后,虽然也能够简单的使用信号与槽完成任务,但是却无法把程序写好。所以,加深对信号与槽的认知非常有必要。 学习!分享!感谢!
  • 在对于重载的信号中,Qt4信号和槽表达优于Qt5信号和槽,Qt5信号和槽兼容Qt4信号和槽兼容。 所谓 GUI 界面,归根结底,就是一堆组件的叠加。我们创建一个窗口,把按钮放上面,把图标放上面,这样就成了一个界面。在...
  • 6.Qt信号与信号连接、Qt4连接写法

    千次阅读 2020-03-26 15:43:37
    更改代码 #include "widget.h" #include "ui_widget.h" #include <QDebug> #include <QPushButton> //Teacher类 //Student类 //下课后老师会触发一个信号(饿了),学生响应信号(请客吃饭) Widget::...
  • QT信号机制

    2013-10-08 10:49:15
    Qt用预编译器和宏来保证强大的跨平台能力,信号机制则是其中最精妙之处。本文分析了几种常见的信号处理机制,然后详细介绍了Qt的Signal/Slot机制。  关键词:信号机制Signal/Slot Win32 MFC Linux Qt  首先要...
  • Qt信号槽-原理分析

    2019-07-12 07:15:30
    文章目录一、问题二、Moc1、变量2、Q_OBJECT展开后的函数声明a、qt_static_metacallb、staticMetaObjectc、metaObjectd、qt_metacaste、qt_metacall3、自定义信号三、connect四、信号触发1、直连2、队列连接五、总结...
  • Qt 信号槽的实现

    2014-06-13 22:03:31
    Qt 信号槽的实现  豆子 2012 年 12 月 05 日 Qt 5条评论 原文地址:http://woboq.com/blog/how-qt-signals-slots-work.html Qt 因其信号槽机制闻名遐迩。但是信号槽是怎样工作的?本文将解释 ...
  • 基于Qt4.8.6版本一、信号槽机制的原理1、信号槽简介信号槽是观察者模式的一种实现,特性如下:A、一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知;B、一个槽就是一个观察者,通常就是在被观察...
  • QT信号与槽机制

    千次阅读 2018-02-27 09:24:23
    信号与槽的基本知识:信号是一个特定的标识,一个槽就是一个函数(只是与一般的函数不同),槽函数能够和信号关联,也能够像普通函数一样直接调用。其实信号与槽理解起来很简单,你可以理解为田径运动赛跑,当信号枪...
  • 【QT】QT信号槽初实现

    2015-09-21 14:14:00
    qt designer随便做了个界面,然后放上VS2010去。界面成功运行了,但是按钮事件一直失败,点击毫无反应。Debug了一下发现QObject::connect(pushButton,SIGNAL(clicked()),this,SLOT(add()));的返回值是false。但是...
  • 参考 https://blog.csdn.net/lcalqf/article/details/52781463connect用于连接qt信号和槽,在qt编程过程中不可或缺。它其实有第五个参数,只是一般使用默认值,在满足某些特殊需求的时候可能需要手动设置。Qt::...
  • Qt信号槽机制详解

    2020-08-12 13:34:27
    信号槽机制是Qt的核心特性之一,也是Qt和其他框架的差别。   其他框架使用回调实现这种通信。 回调是指向函数的指针,因此如果您希望处理函数通知您某些事件,则将指针传递给处理函数的另一个函数(回调)。 然后...
  • 浅谈Qt信号

    2014-12-23 17:35:25
     Qt设计UI的时候,依赖信号槽机制进行通信,以下图为例:  通常子对象的通信可以通过父对象来转发。上图中,QAChildWidget发送信号给父窗口QAWidget, QAWidget转发信号给父窗口QFatherWidget, QFatherWidget来...
  • 探究Qt信号槽以及参数传递 很多文章都写“Qt信号槽的四种连接方式”,实际使用后,感觉这种说法略有误导,这里也记录一下。 假设有如下几个类及其实例: class MySender : public QWidget { Q_OBJECT public: ...
  • Qt 信号槽机制

    千次阅读 2014-04-04 14:36:37
    本文将展示 Qt5 的部分代码,不过有时会为格式化以及简洁性而有所修改信号和槽  首先,我们通过  官方示例 回忆下信号槽是如何工作的。  我们的头文件是这样的: Language: C
  • Qt信号与槽机制详解

    千次阅读 2014-06-03 10:08:11
    信号和槽机制是Qt的核心机制之一,要掌握Qt编程就需要对信号和槽有所了解。信号和槽是一种高级接口,它们被应用于对象之间的通信,它们是Qt的核心特性,也是Qt不同于其它同类工具包的重要地方之一。 在我们所了解...
  • QT信号槽connect函数详解

    万次阅读 2018-10-29 17:21:24
    使用QT开发时,通常使用connect函数只传递四个参数: connect(Sender,SIGNAL(signal),Receiver,SLOT(slot)); 所以我们有可能认为该函数就只有四个参数,但实际上是有第五个参数的,只是通常该函数已经给第五个参数...
  • QtQt信号与槽

    千次阅读 2019-03-26 21:46:38
    00. 目录 文章目录00. 目录01. 信号与槽02. 介绍03. 信号与槽04. 信号05....信号和插槽机制是Qt的核心功能,可能是与其他框架提供的功能最不同的部分。Qt的元对象系统使信号和插槽成为可能。 02. 介...
  • 有重载的信号 如果信号有重载,比如我们向 Newspaper 类增加一个新的信号: void newPaper(const QString &name, const QDate &date);...此时如果还是按照前面的写法,...回想一下 Qt 4 中的处理。在...
  • 本文介绍Qt的新旧两种信号合槽方法,其中新的方法在QT4.8中开始使用,QT4.8以后的版本建议使用新的方法。 函数声明: QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const ...
  • QT信号和槽

    2011-11-21 15:48:22
    信号和槽是QT的核心,在其他GUI中大多采用消息和回调函数,比如win32中 #include #include int main(int argc, char *argv[]) {  QApplication app(argc, argv);  QPushButton button("Quit");  ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 17,094
精华内容 6,837
关键字:

qt信号修改