Qt 的信号signal的参数是否会在内部被拷贝?
Qt 的信号signal的参数是否会在内部被拷贝?
Qt 信号参数复制机制详解:Direct vs Queued 🧩

适配版本: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&
、T
或 T&&
无关;Queued 的复制依据 QMetaType
可构造/销毁能力。
🧰 自定义类型注意事项
- 传递到 QueuedConnection 的参数类型必须可通过
QMetaType
构造与销毁。 - 若为自定义类型:
- 使用
Q_DECLARE_METATYPE(T)
声明。 - 在使用前注册:
qRegisterMetaType<T>("T")
。
- 使用
- 指针类型特殊处理:如果参数名以
*
结尾,视作QMetaType::VoidStar
,不会复制对象本身,仅复制指针值。
🚀 性能与生命周期建议
- 频繁高频的信号传递且无需跨线程,优先使用 DirectConnection(或默认 Auto 且同线程)。
- 跨线程/异步场景下,QueuedConnection 安全但存在参数复制与事件分发开销。
- 对体积较大的对象:
- 优先传指针或
QSharedPointer<T>
,避免大对象复制。 - 若必须值传递,请确保类型的
QMetaType
构造/销毁开销可接受。
- 优先传指针或
🧪 小测试(自检题)
- 同线程
AutoConnection
默认是什么行为?为什么? const QString&
作为信号参数,QueuedConnection 会复制吗?BlockingQueuedConnection
是否复制参数?它的额外语义是什么?
参考答案:
- 1)等效 Direct,不复制;因为接收者与发送者同线程。
- 2)会复制;Queued 必须复制与
const &
无关。 - 3)不复制;但会阻塞发送线程直到槽执行完成。
📚 源码定位(Qt 5.15.2)
- 连接类型分派与直接调用:
qtbase/src/corelib/kernel/qobject.cpp
:QMetaObject::activate
、doActivate
- 队列封送与复制:
qtbase/src/corelib/kernel/qobject.cpp
:queued_activate
、QMetaCallEvent
构造/析构
- 参数类型解析:
queuedConnectionTypes()
(同文件顶部附近):处理QMetaType
id,指针类型→VoidStar
🏁 结语
Qt 的参数是否复制,核心看连接类型:Direct 不复制、Queued 一定复制、BlockingQueued 不复制但阻塞。掌握这一点,有助于做出正确的 API 设计与性能权衡,避免隐藏的生命周期问题与不必要的开销。
祝你写出更优雅高效的 Qt 信号/槽系统!🎉