信号和槽(QT和sigslot)

Qt的QObject 和QObjectData(句柄实体模式)

去原文看看

1.基类QObject:

QObject是Qt类体系的唯一基类,就象MFC中的CObject和Dephi中的TObject,是Qt各种功能的源头活水

ContractedBlock.gif ExpandedBlockStart.gif Code
#include <QApplication> 
#include 
<QtCore> 
#include 
<QtGui>

int main(int argc, char *argv[]) 

QApplication app(argc, argv);

int size = sizeof(QObject); // QObject的大小是8: 虚函数表指针和   QObjectData *d_ptr; QObject中的数据被封装在QObjectData类

QPushButton
* quit = new QPushButton("Quit"); 
delete quit;

return app.exec(); 
}

设计模式: 句柄实体模式,  以QObject为基类的类一般都是句柄类, 有一个指针(一般情况下这个指针还是私有的,方便以后修改句柄类的实现细节)指向一个实体类(保存全部的数据).

准确的说,Qt的基类其实有两个,一个是QObject,这是句柄类的唯一基类,另一个是QObjectData,这是实体类的基类。

 

QObjectData类定义如下:
class QObjectData {
public:
    virtual ~QObjectData() = 0;
    QObject *q_ptr;  // 实体类对应的句柄类, 使得句柄类和实体类可以双向
    QObject *parent; // 指向QObject的父类
    QObjectList children; //QObject相关的子类列表

    uint isWidget : 1;
    uint pendTimer : 1;
    uint blockSig : 1;
    uint wasDeleted : 1;
    uint ownObjectName : 1;
    uint sendChildEvents : 1;
    uint receiveChildEvents : 1;
    uint unused : 25;
    int postedEvents;
#ifdef QT3_SUPPORT                       // 兼容Qt3下序列化的数据
    int postedChildInsertedEvents;
#else
    int reserved;
#endif
};

只要在类的头文件中使用这两个宏,就可以通过函数直接得到实体类和句柄类的实际类型了:

#define Q_DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } \
    inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } \
    friend class Class##Private;

#define Q_DECLARE_PUBLIC(Class) \
    inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
    inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
    friend class Class;
这里还声明了友元,使得数据类和句柄类连访问权限也不用顾忌了

而且为了cpp文件中调用的方便,更是直接声明了以下两个宏
#define Q_D(Class) Class##Private * const d = d_func() //得到数据类
#define Q_Q(Class) Class * const q = q_func() //得到Qt接口类
好了,使用起来倒是方便了,但是以后局部变量可千万不能声明为d和q了


QObjectList children;
这确实是个大胆的设计,如果系统中产生了1000000个QObject实例(对于大的系统,这个数字很容易达到吧),每个QObject子类平均下来是100(这个数字可能大了),
光这些指针的开销就有1000000*100*4=400M,是够恐怖的,如果我们必须在灵活性和运行开销之间做一个选择的话,无疑Qt选择了前者,对此我也很难评论其中的优劣,
总之,Qt确实在内存中保存了所有类实例的树型结构

 

例子:

按钮类QPushButton
QPushButton的句柄类派生关系是:
QObject
QWidget
  QAbstractButton
   QPushButton
QPushButton的实体类派生关系是:
QObjectData
QObjectPrivate
  QWidgetPrivate
   QAbstractButtonPrivate
    QPushButtonPrivate

一个平行体系, 只不过实体类派生关系中多了一个QObjectPrivate,这个类封装了线程处理,信号和槽机制等具体的实现,可以说它才是Qt实体类中真正起作用的基类,而QObjectData不过是一层浅浅的数据封装而已

 

句柄类和实体类这两条体系是如何构造的?
QPushButton* quit = new QPushButton("Quit");
创建一个Qt的按钮,简简单单一行代码,其实背后大有玄机:


QPushButton::QPushButton(const QString &text, QWidget *parent)
    : QAbstractButton(*new QPushButtonPrivate, parent)
首先QPushButton的构造函数中调用了QAbstractButton的构造函数,同时马上new出来一个QPushButtonPrivate实体类,然后把指针转换为引用传递给QAbstractButton

 

ContractedBlock.gif ExpandedBlockStart.gif Code
QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent) 
    : QWidget(dd, parent, 
0
QAbstractButton的构造函数中继续调用基类QWidget的构造函数,同时把QPushButtonPrivate实体类指针继续传给基类

QWidget::QWidget(QWidgetPrivate 
&dd, QWidget* parent, Qt::WFlags f) 
    : QObject(dd, ((parent 
&& (parent->windowType() == Qt::Desktop)) ? 0 : parent)), QPaintDevice() 
QWidget继续坐着同样的事情

QObject::QObject(QObjectPrivate 
&dd, QObject *parent) 
    : d_ptr(
&dd) 
终于到了基类QObject,这里就直接把QPushButtonPrivate的指针赋值给了d_ptr(还记得这个变量名称吧)

最终在QPushButton构造时同时产生的new QPushButtonPrivate被写到了QObject中的d_ptr中

QObject::QObject(QObjectPrivate 
&dd, QObject *parent) 
    : d_ptr(
&dd) 

    Q_D(QObject); 
    ::qt_addObject(d_ptr
->q_ptr = this); 
    QThread 
*currentThread = QThread::currentThread(); 
    d
->thread = currentThread ? QThreadData::get(currentThread)->id : -1
    Q_ASSERT_X(
!parent || parent->d_func()->thread == d->thread, "QObject::QObject()"
               
"Cannot create children for a parent that is in a different thread."); 
    
if (parent && parent->d_func()->thread != d->thread) 
        parent 
= 0
    
if (d->isWidget) { 
        
if (parent) { 
            d
->parent = parent; 
            d
->parent->d_func()->children.append(this); 
        } 
        
// no events sent here, this is done at the end of the QWidget constructor 
    } else { 
        setParent(parent); 
    } 

然后执行QObject的构造函数,这里主要是一些线程的处理,先不理它

QWidget::QWidget(QWidgetPrivate 
&dd, QWidget* parent, Qt::WFlags f) 
    : QObject(dd, ((parent 
&& (parent->windowType() == Qt::Desktop)) ? 0 : parent)), QPaintDevice() 

    d_func()
->init((parent && parent->windowType() == Qt::Desktop ? parent : 0), f); 

然后是QWidget的构造函数,这里调用了数据类QWidgetPrivate的init函数,这个函数不是虚函数,因此静态解析成QWidgetPrivate的init函数调用

QAbstractButton::QAbstractButton(QAbstractButtonPrivate 
&dd, QWidget *parent) 
    : QWidget(dd, parent, 
0

    Q_D(QAbstractButton); 
    d
->init(); 

然后是QAbstractButton的构造函数,这里调用了数据类QAbstractButton的init函数,这个函数不是虚函数,因此静态解析成QAbstractButton的init函数调用

QPushButton::QPushButton(
const QString &text, QWidget *parent) 
    : QAbstractButton(
*new QPushButtonPrivate, parent) 

    Q_D(QPushButton); 
    d
->init(); 
    setText(text); 

然后是QPushButton的构造函数,这里调用了数据类QPushButton的init函数,这个函数不是虚函数,因此静态解析成QPushButton的init函数调用

 

总结一下:
QPushButton在构造的时候同时生成了QPushButtonPrivate指针,QPushButtonPrivate创建时依次调用数据类基类的构造函数;
QPushButton的构造函数中显示的调用了基类的构造函数并把QPushButtonPrivate指针传递过去,QPushButton创建时依次调用接口类基类的构造函数;
在接口类的构造函数中调用了平行数据类的init函数,因为这个函数不是虚函数,因此就就是此次调用了数据类的init函数.

为什么QPushButtonPrivate实体类指针要转换为引用呢?为什么不是直接传递指针?结论是人家喜欢这样写,就是不传指针传引用,而且要用一个*new之类的怪异语法, 真叫人没有办法,其实这里用指针是一样的,代码看起来也自然一些.

delete quit;
说完了构造,再说说析构

 

ContractedBlock.gif ExpandedBlockStart.gif Code
QPushButton::~QPushButton() 


这里当然会调用QPushButton的析构函数了

QAbstractButton::
~QAbstractButton() 

#ifndef QT_NO_BUTTONGROUP 
    Q_D(QAbstractButton); 
    
if (d->group) 
        d
->group->removeButton(this); 
#endif 

然后是QAbstractButton的析构函数

QWidget::
~QWidget() 

    Q_D(QWidget); 
dot.gif 

然后是QWidget的析构函数,这里洋洋洒洒一大堆代码,先不管它

QObject::
~QObject() 

dot.gif 

最后是QObject的析构函数,这里也是洋洋洒洒的一大堆 
    Q_D(QObject); 
    
if (d->wasDeleted) { 
#if defined(QT_DEBUG) 
        qWarning(
"Double QObject deletion detected"); 
#endif 
        
return
    } 
    d
->wasDeleted = true
这些没有什么好说的,就是设一个wasDeleted的标志,防止再被引用,对于单线程情况下,马上就要被删除了,还搞什么标记啊,根本没用,但是对于多线程情况下,这个标记应该是有用的

    
// set all QPointers for this object to zero 
    GuardHash *hash = ::guardHash(); 
    
if (hash) { 
        QWriteLocker locker(guardHashLock()); 
        GuardHash::iterator it 
= hash->find(this); 
        
const GuardHash::iterator end = hash->end(); 
        
while (it.key() == this && it != end) { 
            
*it.value() = 0
            it 
= hash->erase(it); 
        } 
    } 
这里是支持QPointers的实现代码,我们以后再说

    emit destroyed(
this); 
Qt的一个指针删除时要发送destroyed信号,一般情况下是没有槽来响应的

    QConnectionList 
*list = ::connectionList(); 
    
if (list) { 
        QWriteLocker locker(
&list->lock); 
        list
->remove(this); 
    } 
这里清除了信号槽机制中的记录

    
if (d->pendTimer) { 
        
// have pending timers 
        QThread *thr = thread(); 
        
if (thr || d->thread == 0) { 
            
// don't unregister timers in the wrong thread 
            QAbstractEventDispatcher *eventDispatcher = QAbstractEventDispatcher::instance(thr); 
            
if (eventDispatcher) 
                eventDispatcher
->unregisterTimers(this); 
        } 
    } 
这里清除定时器

    d
->eventFilters.clear(); 
这里清除事件过滤机制

    
// delete children objects 
    if (!d->children.isEmpty()) { 
        qDeleteAll(d
->children); 
        d
->children.clear(); 
    } 
这里清除所有子类指针,当然每个子类指针清除时又会清除它的所有子类,因此Qt中new出来的指针很少有显示对应的delete,因为只要最上面的指针被框架删除了, 
它所连带的所有子类都被自动删除了

    { 
        QWriteLocker locker(QObjectPrivate::readWriteLock()); 
        ::qt_removeObject(
this);

        
/* 
          theoretically, we cannot check d->postedEvents without 
          holding the postEventList.mutex for the object's thread, 
          but since we hold the QObjectPrivate::readWriteLock(), 
          nothing can go into QCoreApplication::postEvent(), which 
          effectively means noone can post new events, which is what 
          we are trying to prevent. this means we can safely check 
          d->postedEvents, since we are fairly sure it will not 
          change (it could, but only by decreasing, i.e. removing 
          posted events from a differebnt thread) 
        
*/ 
        
if (d->postedEvents > 0
            QCoreApplication::removePostedEvents(
this); 
    }

    
if (d->parent)        // remove it from parent object 
        d->setParent_helper(0);

    delete d; 
    d_ptr 
= 0
这里要删除相关的数据类指针了

 

Qt之信号和槽机制

 在GUI程序设计中,通常我们希望当对一个窗口部件(widget)进行改变时能告知另一个对此改变感兴趣的窗口部件。更一般的,我们希望任何一类的对象(object)都能和其他对象进行通信。例如,如果用户单击一个关闭按钮,我们可能就希望窗口的 close() 函数被调用。

早期的工具包用回调(backcalls)的方式实现上面所提到的对象间的通信。回调是指一个函数的指针,因此如果你希望一个处理函数通知你一些事情,你可以传递另一个函数(回调函数)指针给这个处理函数。这个处理函数就会在适当的时候调用回调函数。回调有两个重要的缺陷:首先,它们不是类型安全的。我们无法确定处理函数是用正确的参数调用这个回调函数。其次,回调与处理函数紧密的联系在一起以致处理函数必须知道调用哪个回调.

Qt的信号和槽机制是Qt的一大特点, 和MFC中的消息映射机制相似, 就是发送一个消息然后让其它窗口响应,当然,这里的消息是广义的说法,简单点说就是如何在一个类的一个函数中触发另一个类的另一个函数调用,而且还要把相关的参数传递过去.好像这和回调函数也有点关系,但是消息机制可比回调函数有用多了,也复杂多了

Qt和MFC没有采用C++中的虚函数机制, 原因是消息太多, 虚函数开销太大.

多态的底层实现机制只有两种,一种是按照名称查表,一种是按照位置查表,两种方式各有利弊,而C++的虚函数机制无条件的采用了后者,导致的问题就是在子类很少重载基类实现的时候开销太大,再加上象界面编程这样子类众多的情况,基本上C++的虚函数机制就废掉了,于是各家库的编写者就只好自谋生路了,说到底,这确实是C++语言本身的缺陷

消息和槽

在QT中,我们使用一种可替代回调的技术:信号和槽机制。当一个特别的事件产生时则发出一个信号。QT的窗口部件有很多已经预定义好的信号,我们也可以通过继承,给窗口部件的子类添加他们自己信号。槽就是一个可以被调用处理特定信号的函数。QT的窗口部件有很多预定义好的槽,但是通常的做法是给子类窗口部件添加自己的信号,这样就可以操纵自己加入的信号了。

e71dbf52820b63120df3e354.jpg

信号和槽机制是类型安全的:一个信号的签名必须和该信号接受槽的签名相匹配。(事实上以一个槽的签名可以比他可接受的信号的签名少,因为它可以忽略一些签名)。因此签名是一致的,编译器就可以帮助我们检测类型匹配。信号和槽是松耦合的:一个类不知道也不关心哪个槽接受了它所发出的信号。QT的信号和槽机制确保他们自生的正确连接,槽会在正确的时间使用信号参数而被调用。信号和槽可以使用任何数量、任何类型的参数。他们完全是类型安全的。

所有继承至QObject或是其子类(如 QWidget)的类都可包含信号和槽。当对象改变它们自身状态的时候,信号被发送,从某种意义上讲,它们也许对外面的世界感兴趣。这就是所有对象在通讯时所做的一切。它不知道也不关心有没有其他的东西接受它发出的信号。这就是真正的消息封装,并且确保对象可用作一个软件组件。

槽被用于接收信号,但是他们也是正常的成员函数。正如一个对象不知道是否有东西接受了他信号,一个槽也不知道它是否被某个信号连接。这就确保QT能创建真正独立的组件。

你可以将任意个信号连接到你想连接的信号槽,并且在需要时可将一个信号连接到多个槽。将信号直接连接到另一个信号也是可能的(这样无论何时当第一个信号被发出后会立即发出第二个)。

总体来看,信号和槽构成了一个强有力的组件编程机制。

 

示例代码:

ContractedBlock.gif ExpandedBlockStart.gif Code
#include <QApplication> 
#include 
<QPushButton> 
#include 
<QPointer>

int main(int argc, char *argv[]) 

QApplication app(argc, argv);

    QPushButton quit(
"Quit"); 
    quit.resize(
10030); 
    quit.show(); 
    QObject::connect(
&quit, SIGNAL(clicked()), &app, SLOT(quit())); 
    
return app.exec(); 
}

 

Qt的信号槽机制其实就是按照名称查表,因此这里的首要问题是如何构造这个表?
和C++虚函数表机制类似的,在Qt中,这个表就是元数据表,Qt中的元数据表最大的作用就是支持信号槽机制,当然,也可以在此基础上扩展出更多的功能,因为元数据是我们可以直接访问到的,不再是象虚函数表那样被编译器遮遮掩掩的藏了起来,不过Qt似乎还没有完全发挥元数据的能力,动态属性,反射之类的机制好像还没有。

任何从QObject派生的类都包含了自己的元数据模型,一般是通过宏Q_OBJECT定义的
#define Q_OBJECT \
public: \
    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:

首先声明了一个QMetaObject类型的静态成员变量,这就是元数据的数据结构

struct Q_CORE_EXPORT QMetaObject
{
...
    struct { // private data
        const QMetaObject *superdata;//这是元数据代表的类的基类的元数据
        const char *stringdata;//这是元数据的签名标记
        const uint *data;//这是元数据的索引数组的指针
        const QMetaObject **extradata;//这是扩展元数据表的指针,一般是不用的    
    } d;
}
   

宏QT_TR_FUNCTIONS是和翻译相关的
#  define QT_TR_FUNCTIONS \
    static inline QString tr(const char *s, const char *c = 0) \
        { return staticMetaObject.tr(s, c); }

 

例子:

QPushButton的元数据表如下:

static const uint qt_meta_data_QPushButton[] = {

// content:
       1,       // revision  版本号是1
       0,       // classname 类名存储在qt_meta_stringdata_QPushButton中,索引是0,因此就是QPushButton了
       0,    0, // classinfo  类信息数量为0,数据也是0
       2,   10, // methods  QPushButton有2个自定义方法,方法数据存储在qt_meta_data_QPushButton中,索引是10,就是下面的slots:开始的地方
       3,   20, // properties QPushButton有3个自定义属性,属性数据存储在qt_meta_data_QPushButton中,索引是20,就是下面的properties:开始的地方
       0,    0, // enums/sets QPushButton没有自定义的枚举

// slots: signature, parameters, type, tag, flags
      13,   12,   12,   12, 0x0a,
      第一个自定义方法的签名存储在qt_meta_data_QPushButton中,索引是13,就是showMenu()了
      24,   12,   12,   12, 0x08,
      第二个自定义方法的签名存储在qt_meta_data_QPushButton中,索引是24,popupPressed()了

// properties: name, type, flags
      44,   39, 0x01095103,  
      第一个自定义属性的签名存储在qt_meta_data_QPushButton中,索引是44,就是autoDefault了
      第一个自定义属性的类型存储在qt_meta_data_QPushButton中,索引是39,就是bool
      56,   39, 0x01095103,  
      第二个自定义属性的签名存储在qt_meta_data_QPushButton中,索引是56,就是default了
      第二个自定义属性的类型存储在qt_meta_data_QPushButton中,索引是39,就是bool
      64,   39, 0x01095103,  
      第三个自定义属性的签名存储在qt_meta_data_QPushButton中,索引是64,就是flat了
      第三个自定义属性的类型存储在qt_meta_data_QPushButton中,索引是39,就是bool

       0        // eod 元数据的结束标记
};

 

static const char qt_meta_stringdata_QPushButton[] = {
    "QPushButton\0\0showMenu()\0popupPressed()\0bool\0autoDefault\0default\0"
    "flat\0"
};

const QMetaObject QPushButton::staticMetaObject = {  // 填充staticMetaObject
    { &QAbstractButton::staticMetaObject, qt_meta_stringdata_QPushButton,
      qt_meta_data_QPushButton, 0 }

};

静态成员staticMetaObject被填充了
        const QMetaObject *superdata;//这是元数据代表的类的基类的元数据,被填充为基类的元数据指针&QAbstractButton::staticMetaObject
        const char *stringdata;//这是元数据的签名标记,被填充为qt_meta_stringdata_QPushButton
        const uint *data;//这是元数据的索引数组的指针,被填充为qt_meta_data_QPushButton
        const QMetaObject **extradata;//这是扩展元数据表的指针,一般是不用的,被填充为0

首先应该看qt_meta_data_QPushButton,因为这里是元数据的主要数据,它被填充为一个整数数组,正因为这里只有整数,不能有任何字符串存在,因此才有qt_meta_stringdata_QPushButton发挥作用的机会,可以说真正的元数据应该是qt_meta_data_QPushButton加上qt_meta_stringdata_QPushButton,那么为什么不把这两个东西合在一起呢?估计是两者都不是定长的结构,合在一起反而麻烦吧

qt_meta_data_QPushButton实际上是以以下结构开头的

struct QMetaObjectPrivate
{
    int revision;
    int className;
    int classInfoCount, classInfoData;
    int methodCount, methodData;
    int propertyCount, propertyData;
    int enumeratorCount, enumeratorData;
};

一般使用中是直接使用以下函数做个转换
static inline const QMetaObjectPrivate *priv(const uint* data)
{ return reinterpret_cast<const QMetaObjectPrivate*>(data); }

 

信号和槽连接

  QObject::connect(&quit, SIGNAL(clicked()), &app, SLOT(quit()));
// connect的源码, connect函数是连接信号和槽的桥梁,非常关键


bool QObject::connect(const QObject *sender, const char *signal,
                      const QObject *receiver, const char *method,
                      Qt::ConnectionType type)
{
#ifndef QT_NO_DEBUG
    bool warnCompat = true;
#endif
    if (type == Qt::AutoCompatConnection) {
        type = Qt::AutoConnection;
#ifndef QT_NO_DEBUG
        warnCompat = false;
#endif
    }

// 不允许空输入
    if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
#ifndef QT_NO_DEBUG
        qWarning("Object::connect: Cannot connect %s::%s to %s::%s",
                 sender ? sender->metaObject()->className() : "(null)",
                 signal ? signal+1 : "(null)",
                 receiver ? receiver->metaObject()->className() : "(null)",
                 method ? method+1 : "(null)");
#endif
        return false;
    }
    QByteArray tmp_signal_name;

#ifndef QT_NO_DEBUG
// 检查是否是信号标记
    if (!check_signal_macro(sender, signal, "connect", "bind"))
        return false;
#endif
// 得到元数据类
    const QMetaObject *smeta = sender->metaObject();
    ++signal; //skip code跳过信号标记,直接得到信号标识
// 得到信号的索引
    int signal_index = smeta->indexOfSignal(signal);
    if (signal_index < 0) {
        // check for normalized signatures
        tmp_signal_name = QMetaObject::normalizedSignature(signal).prepend(*(signal - 1));
        signal = tmp_signal_name.constData() + 1;
        signal_index = smeta->indexOfSignal(signal);
        if (signal_index < 0) {
#ifndef QT_NO_DEBUG
            err_method_notfound(QSIGNAL_CODE, sender, signal, "connect");
            err_info_about_objects("connect", sender, receiver);
#endif
            return false;
        }
    }

    QByteArray tmp_method_name;
    int membcode = method[0] - '0';

#ifndef QT_NO_DEBUG
// 检查是否是槽,用QSLOT_CODE 1标记
    if (!check_method_code(membcode, receiver, method, "connect"))
        return false;
#endif
    ++method; // skip code

  // 得到元数据类
    const QMetaObject *rmeta = receiver->metaObject();
    int method_index = -1;
// 这里是一个case,信号即可以和信号连接也可以和槽连接
    switch (membcode) {
    case QSLOT_CODE:
  // 得到槽的索引
        method_index = rmeta->indexOfSlot(method);
        break;
    case QSIGNAL_CODE:
  // 得到信号的索引
        method_index = rmeta->indexOfSignal(method);
        break;
    }
    if (method_index < 0) {
        // check for normalized methods
        tmp_method_name = QMetaObject::normalizedSignature(method);
        method = tmp_method_name.constData();
        switch (membcode) {
        case QSLOT_CODE:
            method_index = rmeta->indexOfSlot(method);
            break;
        case QSIGNAL_CODE:
            method_index = rmeta->indexOfSignal(method);
            break;
        }
    }

    if (method_index < 0) {
#ifndef QT_NO_DEBUG
        err_method_notfound(membcode, receiver, method, "connect");
        err_info_about_objects("connect", sender, receiver);
#endif
        return false;
    }
#ifndef QT_NO_DEBUG
// 检查参数,信号和槽的参数必须一致,槽的参数也可以小于信号的参数
    if (!QMetaObject::checkConnectArgs(signal, method)) {
        qWarning("Object::connect: Incompatible sender/receiver arguments"
                 "\n\t%s::%s --> %s::%s",
                 sender->metaObject()->className(), signal,
                 receiver->metaObject()->className(), method);
        return false;
    }
#endif

    int *types = 0;
    if (type == Qt::QueuedConnection
            && !(types = ::queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
        return false;

#ifndef QT_NO_DEBUG
    {
  // 得到方法的元数据
        QMetaMethod smethod = smeta->method(signal_index);
        QMetaMethod rmethod = rmeta->method(method_index);
        if (warnCompat) {
            if(smethod.attributes() & QMetaMethod::Compatibility) {
                if (!(rmethod.attributes() & QMetaMethod::Compatibility))
                    qWarning("Object::connect: Connecting from COMPAT signal (%s::%s).", smeta->className(), signal);
            } else if(rmethod.attributes() & QMetaMethod::Compatibility && membcode != QSIGNAL_CODE) {
                qWarning("Object::connect: Connecting from %s::%s to COMPAT slot (%s::%s).",
                         smeta->className(), signal, rmeta->className(), method);
            }
        }
    }
#endif
// 调用元数据类的连接
    QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
// 发送连接的通知,现在的实现是空的
    const_cast<QObject*>(sender)->connectNotify(signal - 1);
    return true;
}

检查信号标记其实比较简单,就是用signal的第一个字符和用QSIGNAL_CODE=2的标记比较而已
static bool check_signal_macro(const QObject *sender, const char *signal,
                                const char *func, const char *op)
{
    int sigcode = (int)(*signal) - '0';
    if (sigcode != QSIGNAL_CODE) {
        if (sigcode == QSLOT_CODE)
            qWarning("Object::%s: Attempt to %s non-signal %s::%s",
                     func, op, sender->metaObject()->className(), signal+1);
        else
            qWarning("Object::%s: Use the SIGNAL macro to %s %s::%s",
                     func, op, sender->metaObject()->className(), signal);
        return false;
    }
    return true;
}

得到信号的索引实际上要依次找每个基类的元数据,得到的偏移也是所有元数据表加在一起后的一个索引
int QMetaObject::indexOfSignal(const char *signal) const
{
    int i = -1;
    const QMetaObject *m = this;
    while (m && i < 0) {
  // 根据方法的数目倒序的查找
        for (i = priv(m->d.data)->methodCount-1; i >= 0; --i)
   // 得到该方法的类型
            if ((m->d.data[priv(m->d.data)->methodData + 5*i + 4] & MethodTypeMask) == MethodSignal
                && strcmp(signal, m->d.stringdata
       // 得到方法名称的偏移
                          + m->d.data[priv(m->d.data)->methodData + 5*i]) == 0) {
        //如果找到了正确的方法,再增加所有基类的方法偏移量
                i += m->methodOffset();
                break;
            }
   // 在父类中继续找
        m = m->d.superdata;
    }
#ifndef QT_NO_DEBUG
// 判断是否于基类中的冲突
    if (i >= 0 && m && m->d.superdata) {
        int conflict = m->d.superdata->indexOfMethod(signal);
        if (conflict >= 0)
            qWarning("QMetaObject::indexOfSignal:%s: Conflict with %s::%s",
                      m->d.stringdata, m->d.superdata->d.stringdata, signal);
    }
#endif
    return i;
}

// 这里是所有基类的方法偏移量算法,就是累加基类所有的方法数目
int QMetaObject::methodOffset() const
{
    int offset = 0;
    const QMetaObject *m = d.superdata;
    while (m) {
        offset += priv(m->d.data)->methodCount;
        m = m->d.superdata;
    }
    return offset;
}

// 得到方法的元数据
QMetaMethod QMetaObject::method(int index) const
{
    int i = index;
// 要减去基类的偏移
    i -= methodOffset();
// 如果本类找不到,就到基类中去找
    if (i < 0 && d.superdata)
        return d.superdata->method(index);

// 如果找到了,就填充QMetaMethod结构
    QMetaMethod result;
    if (i >= 0 && i < priv(d.data)->methodCount) {
  // 这里是类的元数据
        result.mobj = this;
  // 这里是方法相关数据在data数组中的偏移量
        result.handle = priv(d.data)->methodData + 5*i;
    }
    return result;
}

bool QMetaObject::connect(const QObject *sender, int signal_index,
                          const QObject *receiver, int method_index, int type, int *types)
{
// 得到全局的连接列表
    QConnectionList *list = ::connectionList();
    if (!list)
        return false;
    QWriteLocker locker(&list->lock);
// 增加一个连接
    list->addConnection(const_cast<QObject *>(sender), signal_index,
                        const_cast<QObject *>(receiver), method_index, type, types);
    return true;
}

void QConnectionList::addConnection(QObject *sender, int signal,
                                    QObject *receiver, int method,
                                    int type, int *types)
{
// 构造一个连接
    QConnection c = { sender, signal, receiver, method, 0, 0, types };
    c.type = type; // don't warn on VC++6
    int at = -1;
// 如果有中间被删除的连接,可以重用这个空间
    for (int i = 0; i < unusedConnections.size(); ++i) {
        if (!connections.at(unusedConnections.at(i)).inUse) {
            // reuse an unused connection
            at = unusedConnections.takeAt(i);
            connections[at] = c;
            break;
        }
    }
    if (at == -1) {
        // append new connection
        at = connections.size();
  // 加入一个连接
        connections << c;
    }
// 构造sender,receiver连接的哈希表,加速搜索速度
    sendersHash.insert(sender, at);
    receiversHash.insert(receiver, at);
}

通过connect函数,我们建立了信号和槽的连接,并且把信号类+信号索引+槽类,槽索引作为记录写到了全局的connect列表中

一旦我们发送了信号,就应该调用相关槽中的方法了,这个过程其实就是查找全局的connect列表的过程,当然还要注意其中要对相关的参数打包和解包

// emit是发送信号的代码
void Foo::setValue(int v)
{
if (v != val)
{
  val = v;
  emit valueChanged(v);
}
}

// 发送信号的真正实现在moc里面
// SIGNAL 0
void Foo::valueChanged(int _t1)
{
// 首先把参数打包
    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
// 调用元数据类的激活
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
                           void **argv)
{
// 增加一个基类偏移量
    int offset = m->methodOffset();
    activate(sender, offset + local_signal_index, offset + local_signal_index, argv);
}

void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv)
{
// 这里得到的是QObject的数据,首先判断是否为阻塞设置
    if (sender->d_func()->blockSig)
        return;

// 得到全局链表
    QConnectionList * const list = ::connectionList();
    if (!list)
        return;

    QReadLocker locker(&list->lock);

    void *empty_argv[] = { 0 };
    if (qt_signal_spy_callback_set.signal_begin_callback != 0) {
        locker.unlock();
        qt_signal_spy_callback_set.signal_begin_callback(sender, from_signal_index,
                                                         argv ? argv : empty_argv);
        locker.relock();
    }

// 在sender的哈希表中得到sender的连接
    QConnectionList::Hash::const_iterator it = list->sendersHash.find(sender);
    const QConnectionList::Hash::const_iterator end = list->sendersHash.constEnd();

    if (it == end) {
        if (qt_signal_spy_callback_set.signal_end_callback != 0) {
            locker.unlock();
            qt_signal_spy_callback_set.signal_end_callback(sender, from_signal_index);
            locker.relock();
        }
        return;
    }

    QThread * const currentThread = QThread::currentThread();
    const int currentQThreadId = currentThread ? QThreadData::get(currentThread)->id : -1;

// 记录sender连接的索引
    QVarLengthArray<int> connections;
    for (; it != end && it.key() == sender; ++it) {
        connections.append(it.value());
  // 打上使用标记,因为可能是放在队列中
        list->connections[it.value()].inUse = 1;
    }

    for (int i = 0; i < connections.size(); ++i) {
        const int at = connections.constData()[connections.size() - (i + 1)];
        QConnectionList * const list = ::connectionList();
  // 得到连接
        QConnection &c = list->connections[at];
        c.inUse = 0;
        if (!c.receiver || (c.signal < from_signal_index || c.signal > to_signal_index))
            continue;

  // 判断是否放到队列中
        // determine if this connection should be sent immediately or
        // put into the event queue
        if ((c.type == Qt::AutoConnection
             && (currentQThreadId != sender->d_func()->thread
                 || c.receiver->d_func()->thread != sender->d_func()->thread))
            || (c.type == Qt::QueuedConnection)) {
            ::queued_activate(sender, c, argv);
            continue;
        }

  // 为receiver设置当前发送者
        const int method = c.method;
        QObject * const previousSender = c.receiver->d_func()->currentSender;
        c.receiver->d_func()->currentSender = sender;
        list->lock.unlock();

        if (qt_signal_spy_callback_set.slot_begin_callback != 0)
            qt_signal_spy_callback_set.slot_begin_callback(c.receiver, method, argv ? argv : empty_argv);

#if defined(QT_NO_EXCEPTIONS)
        c.receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
#else
        try {
   // 调用receiver的方法
            c.receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
        } catch (...) {
            list->lock.lockForRead();
            if (c.receiver)
                c.receiver->d_func()->currentSender = previousSender;
            throw;
        }
#endif

        if (qt_signal_spy_callback_set.slot_end_callback != 0)
            qt_signal_spy_callback_set.slot_end_callback(c.receiver, method);

        list->lock.lockForRead();
        if (c.receiver)
            c.receiver->d_func()->currentSender = previousSender;
    }

    if (qt_signal_spy_callback_set.signal_end_callback != 0) {
        locker.unlock();
        qt_signal_spy_callback_set.signal_end_callback(sender, from_signal_index);
        locker.relock();
    }
}

// 响应信号也是在moc里实现的
int Foo::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
// 首先在基类中调用方法,返回的id已经变成当前类的方法id了
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
        switch (_id) {
  // 这里就是真正的调用方法了,注意参数的解包用法
        case 0: valueChanged(*reinterpret_cast< int(*)>(_a[1])); break;
        case 1: setValue(*reinterpret_cast< int(*)>(_a[1])); break;
        }
        _id -= 2;
    }
    return _id;
}

 

sigslot简介


转载www.thecodeway.com

    在开发一个复杂工程的时候,经常会遇到这样一个问题:整个系统被分成数个模块,每个模块提供有限的功能,由上层调用组成整个系统,为了保证每个模块的独立性,我们经常会尽量限制模块与模块之间的直接联系,比如每个模块只提供有限的API或者COM接口,而内部实现则完全封闭起来。
    但有的时候会出一些设计要求,必须能够使模块之间能够直接通讯,而这两个模块往往处于不同的逻辑层次,之间相差甚远,如何设计它们之间的调用模式使整个工程维持整洁变得非常困难,比如模块直接直接包含对方的头文件会引起编译变得复杂,提供api或者接口会引起版本危机等问题。
    sigslot的出现为我们提供了一种解决问题的思想,它用“信号”的概念实现不同模块之间的传输问题,sigslot本身类似于一条通讯电缆,两端提供发送器和接收器,只要把两个模块用这条电缆连接起来就可以实现接口调用,而sigslot本身只是一个轻量级的作品,整个库只有一个.h文件,所以无论处于何种层次的库,都可以非常方便的包含它。

    举个例子,我们设计一个发送消息的类,这个类负责在某种时刻向外界发出求救信号
// Class that sends the notification.
class Sender 
{
public:
    // The signal declaration.
    // The ‘2′ in the name indicates the number of parameters. Parameter types
    // are declared in the template parameter list.
    sigslot::signal2< std::string , int > SignalDanger;
    // When anyone calls Panic(), we will send the SignalDanger signal.
    void Panic()
    {
        SignalDanger("Help!", 0);
    }
};

另外一个类则负责接收求助信号
// Listening class. It must inherit sigslot.
class Receiver : public sigslot::has_slots<>
{
public:
    // When anyone calls Panic(), Receiver::OnDanger gets the message.
    // Notice that the number and type of parameters match
    // those in Sender::SignalDanger, and that it doesn’t return a value.
    void OnDanger(std::string message, int time)
    {
        printf("I heard something like \"%s\" at %d!\n", message.c_str(), time);
    }
};

现在让我们在主逻辑中把这两个类连接起来
Sender sender;
Receiver receiver;
// Receiver registers to get SignalDanger signals.
// When SignalDanger is sent, it is caught by OnDanger().
// Second parameter gives address of the listener function class definition.
// First parameter points to instance of this class to receive notifications.
sender.SignalDanger.connect(&receiver, Receiver::OnDanger);

只要在任何时候调用 sender.Panic()函数,就会把求救信号发送给接收者,而且这两个发送和接收端的模块都可以独立编译,不会出现版本问题。

转载于:https://www.cnblogs.com/dubingsky/archive/2009/06/28/1512596.html

  • 0
    点赞
  • 0
    收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
评论
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值