当前位置: 首页 > news >正文

Qt 的信号signal的参数是否会在内部被拷贝?

Qt 的信号signal的参数是否会在内部被拷贝?


Qt 信号参数复制机制详解:Direct vs Queued 🧩

icon

适配版本:Qt 5.15.2(源码路径基于 qtbase/src/corelib/kernel/qobject.cpp

在这里插入图片描述

目录

    • ✨ 概览
    • ✅ 快速结论
    • 🔧 背后原理(源码走读)
      • 🧭 分派入口:`QMetaObject::activate`
      • 📦 QueuedConnection 如何复制参数
      • 🧵 BlockingQueuedConnection:直接传递 argv
    • 🧠 `const &` 会改变复制行为吗?
    • 🧰 自定义类型注意事项
    • 🚀 性能与生命周期建议
    • 🧪 小测试(自检题)
    • 📚 源码定位(Qt 5.15.2)
    • 🏁 结语


✨ 概览

Qt 信号发射到槽的过程中,参数到底有没有被复制,取决于连接类型:

  • DirectConnection:不复制参数,直接使用发射点准备的 argv 调用槽。
  • QueuedConnection:会复制每个参数的内容(使用 QMetaType::create),并在事件处理完毕后统一销毁(QMetaType::destroy)。
  • BlockingQueuedConnection:特殊的队列连接,直接传递 argv 指针,不做复制;通过信号量阻塞直到处理完成。

这与形参是否使用 const & 无关。只要进入队列(Queued),就需要参数拥有独立生命周期,因此需要复制;如果直接调用(Direct),就不需要复制。


✅ 快速结论

  • DirectConnection:不复制
    • 槽在发射线程立即执行,直接传入 argv
  • QueuedConnection:复制
    • 参数被封送到 QMetaCallEvent 内部,使用 QMetaType::create 创建副本,并由事件析构时销毁。
  • BlockingQueuedConnection:不复制(但阻塞)
    • 直接传递 argv,通过 QSemaphore 实现阻塞等待执行完成。
  • const & 修饰对复制与否无影响
    • 决策点在连接类型而非函数签名;Queued 需要自主管理生命周期,必然复制。

🔧 背后原理(源码走读)

🧭 分派入口:QMetaObject::activate

信号发射最终都会走到 QMetaObject::activate,在这里根据连接类型决定:直接调用还是投递事件。

关键分支(简化要点):

// 1) 需要队列:进入 queued_activate(复制参数并post事件)
if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)|| (c->connectionType == Qt::QueuedConnection)) {queued_activate(sender, signal_index, c, argv);continue;
}// 2) 阻塞队列:构造事件但直接传递 argv,不复制
} else if (c->connectionType == Qt::BlockingQueuedConnection) {QSemaphore semaphore;QMetaCallEvent *ev = c->isSlotObject ?new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore) :new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction,sender, signal_index, argv, &semaphore);QCoreApplication::postEvent(receiver, ev);semaphore.acquire();continue;
}// 3) 直接连接:直接用 argv 调槽,不复制
if (c->isSlotObject) {obj->call(receiver, argv);
} else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv);
} else {QMetaObject::metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv);
}

📦 QueuedConnection 如何复制参数

当判定需要排队时,会调用 queued_activate。在此函数中:

  • 计算 nargs(含返回值槽位)
  • 创建 QMetaCallEvent,为其参数与类型数组分配空间
  • 逐一使用 QMetaType::create(type, argv[n]) 构造每个参数的副本

关键代码要点:

QMetaCallEvent *ev = c->isSlotObject ?new QMetaCallEvent(c->slotObj, sender, signal, nargs) :new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal, nargs);void **args = ev->args();
int *types = ev->types();// 参数类型填充(types[0] 为返回类型槽位,置0;args[0] 置nullptr)
for (int n = 1; n < nargs; ++n)types[n] = argumentTypes[n-1];// 复制每个参数到事件内部
for (int n = 1; n < nargs; ++n)args[n] = QMetaType::create(types[n], argv[n]);QCoreApplication::postEvent(c->receiver.loadRelaxed(), ev);

对应的释放逻辑在 QMetaCallEvent::~QMetaCallEvent()

for (int i = 0; i < d.nargs_; ++i) {if (typeIDs[i] && d.args_[i])QMetaType::destroy(typeIDs[i], d.args_[i]);
}

🧵 BlockingQueuedConnection:直接传递 argv

为阻塞队列连接提供的构造函数注释已明示“直接传递 args,不分配内存”:

// Used for blocking queued connections, just passes args through without allocating any memory.
QMetaCallEvent::QMetaCallEvent(..., void **args, QSemaphore *semaphore)

🧠 const & 会改变复制行为吗?

不会。是否复制只由“是否进入队列(Queued)”决定:

  • Direct/BlockingQueued:直接使用发射点 argv,不复制。
  • Queued:必须复制,确保跨线程/异步投递后的独立生命周期。

与形参是否 const T&TT&& 无关;Queued 的复制依据 QMetaType 可构造/销毁能力。


🧰 自定义类型注意事项

  • 传递到 QueuedConnection 的参数类型必须可通过 QMetaType 构造与销毁。
  • 若为自定义类型:
    • 使用 Q_DECLARE_METATYPE(T) 声明。
    • 在使用前注册:qRegisterMetaType<T>("T")
  • 指针类型特殊处理:如果参数名以 * 结尾,视作 QMetaType::VoidStar,不会复制对象本身,仅复制指针值。

🚀 性能与生命周期建议

  • 频繁高频的信号传递且无需跨线程,优先使用 DirectConnection(或默认 Auto 且同线程)。
  • 跨线程/异步场景下,QueuedConnection 安全但存在参数复制与事件分发开销。
  • 对体积较大的对象:
    • 优先传指针或 QSharedPointer<T>,避免大对象复制。
    • 若必须值传递,请确保类型的 QMetaType 构造/销毁开销可接受。

🧪 小测试(自检题)

  1. 同线程 AutoConnection 默认是什么行为?为什么?
  2. const QString& 作为信号参数,QueuedConnection 会复制吗?
  3. BlockingQueuedConnection 是否复制参数?它的额外语义是什么?

参考答案:

  • 1)等效 Direct,不复制;因为接收者与发送者同线程。
  • 2)会复制;Queued 必须复制与 const & 无关。
  • 3)不复制;但会阻塞发送线程直到槽执行完成。

📚 源码定位(Qt 5.15.2)

  • 连接类型分派与直接调用:
    • qtbase/src/corelib/kernel/qobject.cppQMetaObject::activatedoActivate
  • 队列封送与复制:
    • qtbase/src/corelib/kernel/qobject.cppqueued_activateQMetaCallEvent 构造/析构
  • 参数类型解析:
    • queuedConnectionTypes()(同文件顶部附近):处理 QMetaType id,指针类型→VoidStar

🏁 结语

Qt 的参数是否复制,核心看连接类型:Direct 不复制、Queued 一定复制、BlockingQueued 不复制但阻塞。掌握这一点,有助于做出正确的 API 设计与性能权衡,避免隐藏的生命周期问题与不必要的开销。

祝你写出更优雅高效的 Qt 信号/槽系统!🎉

http://www.dtcms.com/a/361958.html

相关文章:

  • Vue3 中 Proxy 在组件封装中的妙用
  • 【网络安全入门基础教程】网络安全零基础学习方向及需要掌握的技能
  • Electron 应用生命周期管理:app 模块核心 API
  • 【 HarmonyOS 】错误描述:The certificate has expired! 鸿蒙证书过期如何解决?
  • 光学神经网络与人工智能应用
  • 网络流量分析——熟悉Wireshark
  • 【洛谷】【模板】栈、有效的括号、验证栈序列、后缀表达式、(stack相关算法题)
  • 腾讯位置商业授权微信小程序获取城市列表
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(二十三)控件中常用文本格式
  • 玩转OurBMC第二十一期:前端页面仪表盘的设计与使用实践
  • js 海康视频插件的具体步骤
  • QMainWindow使用QTabWidget添加多个QWidget
  • Simulink库文件-一种低通滤波模块搭建方法
  • 优化正则表达式性能:预编译与模式匹配的最佳实践
  • 均值滤波和中值滤波的简介、C语言实现和实测
  • 边缘计算设备 RK3576芯片
  • CGroup 资源控制组 + Docker 网络模式
  • NLP大语言模型数据准备
  • NLP技术突破:浅层与深层语义分析全解析
  • 嵌入式学习(day37) 数据库 Sqlite相关命令函数
  • Salesloft OAuth漏洞影响范围大幅增加,波及所有集成应用
  • 可编辑115页PPT | 某纸制品制造企业数字化转型战略规划项目建议书
  • 闭包的简单讲解
  • 三、数据结构
  • VMware安装
  • 基于docker-compose搭建EFK(Elasticsearch+fluentd+kibana)的日志平台
  • 【高等数学】第十章 重积分——第五节 含参变量的积分
  • python3中的除法/ (会把int变成float)向下取整//(不会改变int类型) 和 直接舍弃小数,向0截断
  • JVM性能监控工具的使用
  • python中的分代垃圾回收机制的原理【python进阶二、2】