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

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.xdnf.cn/news/19428.html

相关文章:

  • duilib中CTextUI控件使用技巧与问题总结(CTextUI控件自适应文字宽度特性)
  • 如何正确使用ChatGPT做数学建模比赛——数学建模AI使用技巧
  • 【macOS】垃圾箱中文件无法清理的“含特殊字符文件名”的方法
  • 开发使用mybatis是用混合模式还是全注解模式
  • 陕西凉拌西瓜皮,变废为宝的陕味美味~
  • JavaScript 性能优化实战技术
  • 【GIS图像处理】有哪些SOTA方法可以用于将1.5米分辨率遥感图像超分辨率至0.8米精度的?
  • mysql实例是什么?
  • Midscenejs自然语言写测试用例
  • 基于uni-app的校园综合服务平台开发实战
  • 大模型落地全流程实践:从技术选型到企业级部署
  • 警告:OPENCV_FFMPEG_READ_ATTEMPTS (current value is 4096)
  • flume接收处理器:构建高可用与高性能的数据链路
  • AR-LSAT 推理任务全解析:从逻辑推理到类比推理的挑战
  • Fabarta个人专属智能体赋能媒体:从过载信息到深度可控的创作体系
  • Claude AI 因编写勒索软件和开展勒索活动而被滥用
  • java基础1
  • DevExpress WinForms中文教程:Data Grid - 过滤编辑器
  • 【机器学习学习笔记】pandas基础
  • matlab-神经网络的语音识别
  • SHELL命令pr
  • 【C++】 Vector容器操作全解析
  • OpenHarmony智能语音框架深度拆解:从VAD到唤醒词打造你的AI语音智能体
  • 第8篇c++Expression: (L“Buffer is too small“ 0
  • 20.30 QLoRA微调终极指南:Hugging Face参数优化实战,24GB显存直降50%性能不减
  • 【JavaScript】async/await 与 Fetch 传参,PUT,PATCH,文件上传,批量删除等前端案例
  • 二、Git基础命令速查表
  • Goframe 框架下HTTP反向代理并支持MCP所需的SSE协议的实现
  • leetcode算法刷题的第二十三天
  • Windows Qt5.15.17源码使用VS2019编译安装