技术点:connect函数的几种写法及连接方式

技术点:connect函数的几种写法及连接方式

沭osTT
0.077
2020.07.21 09:38:09
字数 2,494
阅读 2,336
前言
conncet函数是QT中信号和槽机制的实现函数,那么QT也定义了其几种不同的写法和5种连接类型,下面对这几种方式和类型进行研究。

四种写法
一、老版本标准 connect
connect(btnClick,SIGNAL(Clicked(bool)),this,SLOT(onClicked(bool)));
这种写法比较麻烦,常常在用的时候缺少括号,不过该写法很明确,一眼就能看出来是将哪个信号连接到哪个槽。

二、QT5.0后推出 connect
connect(btnClick,&MyButton::sigClicked,this,&Widget::onClicked);
但是这种写法存在一个致命的缺点,若是信号sigClicked是重载的,connect会无法识别发送的是哪种信号,因此需换另一种写法:

connect(btnClick, static_cast(&MyButton::sigClicked),
this,&Widget::onClicked);
问题又来了,如果我的onClicked槽也是重载的话,还是会报同样的错误。因为编译器不知道你想要真正连接哪个槽。所以这里建议,如果信号重载,可以用上面的方法来写,如果槽重载…还是用第一种方法来 connect 吧,比较保险,虽然比较麻烦点。

三、某版本QT推出 connect
很显然这种写法相对于第二种会比较简单些,但依然不能连接到重载的槽函数,如果连接重载槽函数,还是会报错误。

connect(btnClick,QOverload::of(&MyButton::sigClicked),
this,&Widget::onClicked);
四、Lambda 函数写法
代码格式如下:

[capture] (parameters) mutable ->return-type {statement}

[capture]:捕捉列表。捕捉列表总是出现在Lambda函数的开始处。实际上,[]是Lambda引出符。编译器根据该引出符判断接下来的代码是否是Lambda函数。捕捉列表能够捕捉上下文中的变量以供Lambda函数使用;

(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号“()”一起省略;

mutable:((mutable修饰符。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空);

->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。我们可以在不需要返回值的时候也可以连同符号”->”一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导;

{statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。

与普通函数最大的区别是,除了可以使用参数以外,Lambda函数还可以通过捕获列表访问一些上下文中的数据。具体地,捕捉列表描述了上下文中哪些数据可以被Lambda使用,以及使用方式(以值传递的方式或引用传递的方式)。语法上,在“[]”包括起来的是捕捉列表,捕捉列表由多个捕捉项组成,并以逗号分隔。捕捉列表有以下几种形式:

[var] 表示值传递方式捕捉变量var;
[=] 表示值传递方式捕捉所有父作用域的变量(包括this);
[&var] 表示引用传递捕捉变量var;
[&] 表示引用传递方式捕捉所有父作用域的变量(包括this);
[this] 表示值传递方式捕捉当前的this指针。
上面提到了一个父作用域,也就是包含Lambda函数的语句块,说通俗点就是包含Lambda的“{ }”代码块。上面的捕捉列表还可以进行组合,例如:

[=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量;
[&,a,this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其它所有变量。
不过值得注意的是,捕捉列表不允许变量重复传递。下面一些例子就是典型的重复,会导致编译时期的错误。例如:

[=,a]这里已经以值传递方式捕捉了所有变量,但是重复捕捉a了,会报错的;
[&,&this]这里&已经以引用传递方式捕捉了所有变量,再捕捉this也是一种重复。
connect(btnClick, &MyButton::sigClicked, this{
/----- do something -----/
});
connect(btnClick, QOverload::of(&MyButton::sigClicked),this,
[=](bool check){
/----- do something -----/
});
connect(btnClick, static_cast(&MyButton::sigClicked), this,
[=](bool check){
/----- do something -----/
});
五种连接类型
自动连接 Qt::AutoConnection (0)
(默认)信号发射对象如果和槽的执行对象在同一个线程,那么将是直连方式,否则就是队列方式。
(Default) If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.

直接连接 Qt::DirectConnection(1)
相当于直接调用槽函数,但是当信号发出的线程和槽的对象不再一个线程的时候,则槽函数是在发出信号的线程中执行的。
The slot is invoked immediately when the signal is emitted. The slot is executed in the signalling thread.

队列连接 Qt::QueuedConnection(2)
信号发射后,当事件循环返回到接收线程时槽函数就执行了,也就是说这种连接方式不是立即触发槽函数的,而是要排队等的,并且是在槽函数的线程中执行。
The slot is invoked when control returns to the event loop of the receiver’s thread. The slot is executed in the receiver’s thread.

阻塞队列连接 Qt::BlockingQueuedConnection(3)
在槽函数返回之前信号发射所在的线程都是阻塞的。即发送完信号,发送线程会阻塞,直到槽函数线程运行完毕。
Same as Qt::QueuedConnection, except that the signalling thread blocks until the slot returns. This connection must not be used if the receiver lives in the signalling thread, or else the application will deadlock.

唯一连接 Qt::UniqueConnection(0x80)
防止重复连接。如果当前信号和槽已经连接过了,就不再连接了,即connect连接失效,可通过按位或 | 与以上四个结合在一起使用。
This is a flag that can be combined with any one of the above connection types, using a bitwise OR. When Qt::UniqueConnection is set, QObject::connect() will fail if the connection already exists (i.e. if the same signal is already connected to the same slot for the same pair of objects). This flag was introduced in Qt 4.6.

信号和槽调用机制及安全问题
多线程参数分析
一般来说,使用到connect第五参数,都会涉及到多线程的调用。而在多线程中,发出信号和调用槽的方式分两种:同步调用 & 异步调用

同步调用:发出信号后,当前线程等待槽函数执行完毕后才继续执行。
异步调用:发出信号后,立即执行剩下逻辑,不关心槽函数啥时候执行。
所以也就有了下面这张表:
分类图表
信号参数安全
因为一个信号可以连接多个槽函数,如果参数是T * 或者是T &话会不会第一个槽函数改变参数的值,然后第二此调用的参数就已经不是信号发出的值?

对于T &: 在同步调用中则是变化的,不可用于异步,不可跨线程。所以BlockingQueuedConnection方式的同步也不行。(T& 不可用在队列调用(QueuedConnection)和阻塞调用(BlockingQueuedConnection)中。只能使用const T &。)

因为同步调用,你可以理解成直接调用,那么连接多个槽函数就相当于直接连续调用多个函数。
对于T *,最好不要同时连接多个槽。

对于同步调用:是一个接着一个调用的,执行顺序类似上面,所以值也是每次调用也会变化的。
对于异步调用:其内容确实不确定的,因为异步调用的时间是不可控的。如果还有跨线程相关,则还有线程安全问题。
多线程代码实现
概述
Qt有两种多线程的方法,其中一种是继承QThread的run函数,另外一种是把一个继承于QObject的类转移到一个Thread里。 Qt4.8之前都是使用继承QThread的run这种方法,但是Qt4.8之后,Qt官方建议使用第二种方法。两种方法区别不大,用起来都比较方便,但继承QObject的方法更加灵活。

代码实现

  • 继承于QObject的线程
    /* ----- 自定义线程 -----*/
    #ifndef MYTHREAD_H
    #define MYTHREAD_H
    class MyThread : public QObject
    {
    Q_OBJECT
    public:
    explicit MyThread(QObject *parent = 0){}
    ~MyThread(){}

    /* ----- 线程处理函数 -----*/
    void drawImage(){
    // 图片处理…
    emit updateImage(image);
    }
    signals:
    void updateImage(QImage temp);
    };
    #endif // MYTHREAD_H

/* ----- 主GUI线程 -----*/
#ifndef WIDGET_H
#define WIDGET_H
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0){
myT = new MyThread;
//创建子线程
thread = new QThread(this);
//把自定义线程类添加到子线程
myT->moveToThread(thread);
//启动子线程,不启动线程处理函数
thread->start();
//按钮按下时,调用自定义线程画图函数
connect(ui->pushButton,&QPushButton::pressed,myT,&MyThread::drawImage);
//绘图完毕后发送updateImage信号,更新绘图
connect(myT,&MyThread::updateImage,this,&Widget::getImage);
//关闭窗口时调用关闭子线程的函数
connect(this,&Widget::destroyed,this,dealClose);
}
~Widget(){}
//重写绘图事件
void paintEvent(QPaintEvent *){
QPainter p(this);
p.drawImage(50,50,image);
}
void getImage(QImage){
image = temp;
//更新窗口,间接调用paintevent()
update();
}
void dealClose(){
//停止线程,调用之后并不立即实现
thread->quit();
//等待线程完成当前工作
thread->wait();
//析构线程对象
delete myT;
}
private:
Ui::Widget *ui;
QImage image;
//自定义线程
MyThread *myT;
//子线程
QThread *thread;
};
#endif // WIDGET_H

  • 继承于QThread的线程 【具体应用见二阶段项目】
    继承与QThread的线程使用也很简单,但需要注意的是,thread线程
    里面,只有run函数里的代码是运行在新线程的,其他代码仍然在主
    线程中运行。

#ifndef NETTHERAD_H
#define NETTHERAD_H
#include
class NetTherad : public QThread
{
public:
NetTherad();
void run(){
while(1)
cout<< “get in run.”<<endl;
}
};
/* ----- 测试代码 -----*/
int main(){
NetTherad *netthreadt = new NetTherad;
netthreadt->start(); // 启动线程
}
对于继承QThread的线程,我们如何优雅地结束他呢?
建议使用的方法是在每次循环之前进行一个STOP标志位的判断,当STOP值为假时,退出循环。当run函数里面没有循环时,函数像普通函数一样,运行完一次即退出函数。【也可以使用terminate()粗暴的结束线程,但是不推荐!!!除非是必须执行的。】
线程结束时会发出信号,此时我们可以通过信号槽来销毁线程:

connect(thread,&QThread::finished,thread,&QObject::deleteLater);
//线程结束后调用deleteLater来销毁分配的内存

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值