QObject

QObject

QObject::connect的5种用法

1. 使用SIGNAL()和SLOT()宏(Qt4)

class myobject : public QObject
{
    Q_OBJECT

public:
    myobject(QObject *parent = nullptr):QObject(parent){
    }
signals:
    void signal1(int i);
public slots:
    void slot1(int i){
        qDebug()<<"slot1,i="<<i;
    }
};
#普通函数
void funslot(int i)
{
    qDebug()<<"fun:i="<<i;
}

myobject *obj1 = new  myobject("boj1");
myobject *obj2 = new  myobject("boj2");
QObject::connect(obj1, SIGNAL(signal1(int)), obj2, SLOT(slot1(int)),Qt::AutoConnection);
QObject::connect(obj1, "2signal1(int)", obj2, "1slot1(int)",Qt::AutoConnection);
emit obj1->signal1(123);

注意,信号和槽参数不能包含任何变量名,只能包含类型。名称错误编译阶段无法检测出来
#define SLOT(a) “1”#a
#define SIGNAL(a) “2”#a

最终加入到发送者connect链表里面的连接:
    QMetaObjectPrivate::connect{
    c->method_relative = method_index;
    c->method_offset = method_offset;
    c->connectionType = type;
    c->isSlotObject = false;
    c->argumentTypes.storeRelaxed(types);
    c->callFunction = callFunction;
    }
Connection保存在发送者的SignalVector[signal_index]里面其数据结构层级为:
QObject->QObjectPrivate->ConnectionData->SignalVector->ConnectionList->QObjectPrivate::Connection
SignalVector可以看成是个二维数组[signal_index][ConnectionList],
signal_index为信号函数对应的编号,其从0开始按照声明顺序递增0,1,2,3,4....
每一次connect就相当于在发送对象的SignalVector[signal_index]新增一个QObjectPrivate::Connection
每次disconnect就相当于在发送对象的SignalVector[signal_index]删除一个或多个匹配的QObjectPrivate::Connection

2. 使用类成员函数指针(Qt5新增,并推荐)

QObject::connect(obj1, &myobject::signal1, obj2, &myobject::slot1,Qt::AutoConnection);
QObject::connect(obj1, &myobject::signal1, &myobject::slot1);
QObject::connect(obj1, &myobject::signal1, funslot);

注意,信号和槽地址错误,编译阶段可以检测出来
有重载的情况怎么办?QOverload或static_cast转换解决
最好的做法可能是建议不要重载信号或槽

QObject::connect(obj1, &myobject::signal1, obj2, QOverload<float>::of(&myobject::slot1),Qt::AutoConnection);
QObject::connect(obj1, &myobject::signal1, static_cast<void (myobject::*)(int)>(&myobject::slot1));

优点

  1. 编译时检查是否存在信号和插槽,类型,或者是否缺少Q_OBJECT。
    (但是如果信号参数填的是非信号函数,编译阶段检查不出来,运行时报错signal not found)
    QObject::connect(obj1, &myobject::slot1, &myobject::slot1);
    //QObject::connect: signal not found
  2. 参数可以通过typedef定义,也可以使用不同的名称空间说明符,并且可以使用。
  3. 如果存在隐式转换(例如,从QString到QVariant),则可以自动转换类型。
  4. 可以连接到QObject的任何成员函数,而不仅仅是插槽。
  5. 可以与C++11 lambda表达式一起使用

缺点

  1. 更复杂的语法(您需要指定对象的类型)
  2. 重载载情况下的语法非常复杂
  3. 不再支持插槽中的默认参数。
最终加入到发送者connect链表里面的连接:
std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};
    c->sender = s;
    c->signal_index = signal_index;
    c->receiverThreadData.storeRelaxed(td);
    c->receiver.storeRelaxed(r);
    c->slotObj = slotObj;
    c->connectionType = type;
    c->isSlotObject = true;
    if (types) {
        c->argumentTypes.storeRelaxed(types);
        c->ownArgumentTypes = false;
    }

3. 关联函数

QObject::connect(obj1, &myobject::signal1, funslot);
最终加入到发送者connect链表里面的连接QObjectPrivate::Connection
同使用类成员函数指针

4. Lambda

QObject::connect(obj1, &myobject::signal1, [](int i){
           qDebug()<<"Lambda:i="<<i;
       });
QObject::connect(obj1, &myobject::signal1,obj2, [](int i){
           qDebug()<<"Lambda:i="<<i;
       },Qt::AutoConnection);
最终加入到发送者connect链表里面的连接QObjectPrivate::Connection
同使用类成员函数指针

5. QMetaMethod 很少用到

QMetaMethod sig = obj1->metaObject()->method(obj1->metaObject()->methodOffset()); // void signal1(int i);
QMetaMethod slt = obj2->metaObject()->method(obj2->metaObject()->methodOffset()+1);//void slot1(int i);
QObject::connect(obj1, sig, obj2, slt, Qt::AutoConnection);

可以根据QObject的metaObject()方法获取元对象QMetaObject,然后根据元对象的method方法获取信号或槽的元方法对象QMetaMethod
元对象QMetaObject的methodOffset()函数获取当前对象的第一个信号的位置(不一定为0),
如果没有信号就是第一个槽的位置,按照信号槽函数的声明顺序来的,信号在前面槽在后面
可以打印出来观看:

    for(int i = obj1->metaObject()->methodOffset(); i < metaObject->methodCount(); ++i)
        qDebug() << QString::fromLatin1(metaObject->method(i).methodSignature());

Qt::ConnectionType

1. Qt::AutoConnection

<(默认)如果接收器位于发出信号的线程中,则使用Qt::DirectConnection。否则,使用Qt::QueuedConnection。在发出信号时确定连接类型。
receiverInSameThread判断发送信号对象和接收的槽对象是否在同一个线程
if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                || (c->connectionType == Qt::QueuedConnection)) 
{
       QMetaCallEvent *ev = new QMetaCallEvent(c->slotObj, sender, signal, nargs);
       QCoreApplication::postEvent(receiver, ev);
 }
 else if (c->connectionType == Qt::BlockingQueuedConnection) 
 {
    //信号量会被放入事件里面,在事件析构的时候release
    QSemaphore semaphore;

    {
       QBasicMutexLocker locker(signalSlotLock(sender));
       QMetaCallEvent *ev = new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore);
       QCoreApplication::postEvent(receiver, ev);
    }
    semaphore.acquire();
 }else
 {
     QMetaObject::metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv);
 }            
bool QObject::event(QEvent *e)
{
switch (e->type()) {
        case QEvent::MetaCall:
        {
            QAbstractMetaCallEvent *mce = static_cast<QAbstractMetaCallEvent*>(e);
            QBasicMutexLocker locker(signalSlotLock(this));
            mce->placeMetaCall(this);
            break;
        }
}
}

void QMetaCallEvent::placeMetaCall(QObject *object)
{
        QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod,
                              d.method_offset_ + d.method_relative_, d.args_);
}

QAbstractMetaCallEvent::~QAbstractMetaCallEvent()
{
#if QT_CONFIG(thread)
    if (semaphore_)
        semaphore_->release();
#endif
}

2. Qt::DirectConnection

当发出信号时,立即调用该槽。如果槽函数对象与信号不在同一线程,槽函数也会在信号线程中执行。

3. Qt::QueuedConnection

当控制返回到接收者线程的事件循环时调用该槽。槽在接收者的线程中执行。

4. Qt::BlockingQueuedConnection

与Qt::QueuedConnection相同,除了信号线程阻塞直到插槽返回。
如果接收方位于信号线程中,则不能使用此连接,否则应用程序将死锁。

5. Qt::UniqueConnection

这是一个标志,可以使用位或与上述任何一种连接类型组合使用。当Qt::UniqueConnection设置时,如果连接已经存在,QObject::connect()将失败(即,如果相同的信号已经连接到同一插槽的同一对对象)。这个标志是在Qt 4.6中引入的。

信号与槽

1. 信号的定义

使用关键字:signals 加冒号, 后面声明的函数就是信号,其本质就是函数
其被宏定义为:# define signals public qt_signal
信号不需要也不可以由我们自己实现,因为其会被moc编译器编译成函数
*信号与槽的返回值要兼容

signals: //等于 public qt_signal :
    void signal1(int i);
    void signal2(int i);
    void signal3(int i);

moc编译器编译后的myobject::signal1函数,实现如下:
void myobject::signal1(int _t1)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

2. 信号发送

  1. 使用关键字emit,emit是空的宏定义: # define emit
  2. 信号函数调用前加emit,如 emit obj1->signal1(111);
  3. 由于emit是空的宏定义,所以也可以直接调用信号函数,obj1->signal1(111);

信号发送本质就是函数调用
一般为了区分是信号和普通函数,都会加上emit关键字,提高代码可读性

3. 槽函数

  1. 权限声明后加slots表示后面的为槽函数,如public slots:
  2. 槽函数不支持默认参数
class A:public QObject
{
    Q_OBJECT
signals:
int signal2();
public slots:
    int slot1(int i=100);
};
QObject::connect(obj1, &myobject::signal2, obj2, &myobject::slot1,Qt::AutoConnection);//不支持,编译时报错
QObject::connect(obj1, SIGNAL(signal2()),obj2,SLOT(slot1(int)),Qt::AutoConnection);//运行时报错
emit signal2();

disconnect

1. 取消所有信号关联的所有槽

```
QObject::disconnect(obj1, nullptr, nullptr, nullptr);
obj1->disconnect();
这同时也会取消lambda和普通函数的关联
```

2. 取消某个信号关联的所有槽

```
QObject::disconnect(obj1,SIGNAL(signal1()), nullptr, nullptr);
QObject::disconnect(obj1,&myobject::signal1, nullptr, nullptr);
obj1->disconnect(SIGNAL(signal1()));
这同时也会取消lambda和普通函数的关联
```

3. 取消某个信号关联的某个接收者的所有槽

```
QObject::disconnect(obj1,SIGNAL(signal1()), obj2, nullptr);
QObject::disconnect(obj1,&myobject::signal1, obj2, nullptr);
这个无法取消lambda和普通函数的关联(普通函数接收者为发送者本身)
QObject::disconnect(obj1,&myobject::signal1, obj1, nullptr);
这个可以取消lambda和普通函数的关联
```

4. 取消某个信号关联的某个接收者的某个槽

如果多个相同的关联都将被取消;
对于这种指定具体的信号和槽的关联取消:SIGNAL/SLOT关联的,必须SIGNAL/SLOT方式取消关联,类成员函数关联的比类成员函数取消关联
```
QObject::connect(obj1,SIGNAL(signal1(int)), obj2, SLOT(slot1(int)));
QObject::disconnect(obj1,SIGNAL(signal1(int)), obj2, SLOT(slot1(int)));

QObject::connect(obj1, &myobject::signal1, obj2, &myobject::slot1);
QObject::disconnect(obj1, &myobject::signal1, obj2, &myobject::slot1);
下面的将无法被取消:
QObject::connect(obj1,SIGNAL(signal1(int)), obj2, SLOT(slot1(int)));
QObject::disconnect(obj1, &myobject::signal1, obj2, &myobject::slot1);
下面的将无法被取消:
QObject::connect(obj1, &myobject::signal1, obj2, &myobject::slot1);
QObject::disconnect(obj1,SIGNAL(signal1(int)), obj2, SLOT(slot1(int)));

具体原因如下:
就是这两种不同方法添加的QObjectPrivate::Connection不一样,具体到槽的匹配永远无法找不到。
disconnect最终都将走到这一步进行匹配:
 if (signal_index < 0) {//如果disconnect传入的signal为nullptr,删除所有信号的所有连接者
        for (int sig_index = -1; sig_index < scd->signalVectorCount(); ++sig_index) {
            disconnectHelper(..., signal_index, receiver, method_index, slot, ...); 
        }
}else
{
     disconnectHelper(..., signal_index, receiver, method_index, slot, ...); 
}
disconnectHelper实现如下
auto &connectionList = connections->connectionsForSignal(signal_index);//取出对应信号的连接链表
auto *c = connectionList.first.loadRelaxed();
while (c) { //遍历signal_index信号链表的所有接收者
    QObject *r = c->receiver.loadRelaxed();//获取当前链表节点的接收者
    if (r)
    {
        if(receiver == nullptr ){//如果disconnect传入的接收者为nullptr,直接删除连接者
                connections->removeConnection(c);
        }
        else if(r == receiver)//如果不为空判断连接者是否相同
        {
            //使用类成员函数指针connect的method_index<0,并且是isSlotObject=true
            //使用SIGNAL()和SLOT()宏connect的method_index>0,并且是isSlotObject=false
            if((method_index < 0 || (!c->isSlotObject && c->method() == method_index))
            {
                if((slot == nullptr || (c->isSlotObject && c->slotObj->compare(slot))
                {
                    connections->removeConnection(c);
                }	
            }
            //上面的判断总结为:
            //如果	使用类成员函数指针connect的比较	c->slotObj->compare(slot)
            //如果	使用SIGNAL方式connect的比较c->method() == method_index
            //所以这两种不同的connect,disconnect时具体到槽的匹配永远无法找不到。
        }
    }
    c = c->nextConnectionList.loadRelaxed();
}
```

5. 单独取消关联的某个普通函数和Lambda表达式

使用连接时返回的QMetaObject::Connection变量,取消关联对应连接
```
auto func =QObject::connect(obj1,&myobject::signal1,funslot);
QMetaObject::Connection lambdac = QObject::connect(obj1,&myobject::signal1,[](int a){
  qDebug("lambda %d\n",a);
});
 QObject::disconnect(func);
 QObject::disconnect(lambdac);
```

6. 取消某个接收者对象的所有关联槽

```
QObject::disconnect(obj1, nullptr,obj2, nullptr);
obj1->disconnect(obj2);
```	

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值