2015 年 4 月 20 日更新
最初我认为传递对堆栈分配对象的引用等同于传递该对象的地址。因此,在没有存储副本(或共享指针)的包装器的情况下,排队的槽连接可能会使用坏数据结束。
但@BenjaminT 和@cgmb 引起我的注意,Qt 实际上确实对 const 引用参数进行了特殊处理。它将调用复制构造函数并存放复制的对象以用于插槽调用。即使您传递的原始对象在插槽运行时已被销毁,插槽获取的引用也将完全指向不同的对象。
您可以阅读@cgmb's answer 了解机械细节。但这里有一个快速测试:
#include <iostream>
#include <QCoreApplication>
#include <QDebug>
#include <QTimer>
class Param {
public:
Param () {}
Param (Param const &) {
std::cout << "Calling Copy Constructor\n";
}
};
class Test : public QObject {
Q_OBJECT
public:
Test () {
for (int index = 0; index < 3; index++)
connect(this, &Test::transmit, this, &Test::receive,
Qt::QueuedConnection);
}
void run() {
Param p;
std::cout << "transmitting with " << &p << " as parameter\n";
emit transmit(p);
QTimer::singleShot(200, qApp, &QCoreApplication::quit);
}
signals:
void transmit(Param const & p);
public slots:
void receive(Param const & p) {
std::cout << "receive called with " << &p << " as parameter\n";
}
};
...还有一个主要的:
#include <QCoreApplication>
#include <QTimer>
#include "param.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// name "Param" must match type name for references to work (?)
qRegisterMetaType<Param>("Param");
Test t;
QTimer::singleShot(200, qApp, QCoreApplication::quit);
return a.exec();
}
运行这个演示了对于 3 个插槽连接中的每一个,通过复制构造函数制作了一个单独的 Param 副本:
Calling Copy Constructor
Calling Copy Constructor
Calling Copy Constructor
receive called with 0x1bbf7c0 as parameter
receive called with 0x1bbf8a0 as parameter
receive called with 0x1bbfa00 as parameter
您可能想知道如果 Qt 只是要进行复制,那么“通过引用传递”有什么好处。但是,它并不总是复制...这取决于连接类型。如果您更改为Qt::DirectConnection,则不会进行任何复制:
transmitting with 0x7ffebf241147 as parameter
receive called with 0x7ffebf241147 as parameter
receive called with 0x7ffebf241147 as parameter
receive called with 0x7ffebf241147 as parameter
如果你切换到按值传递,你实际上会得到一个更中间的副本,尤其是在Qt::QueuedConnection 的情况下:
Calling Copy Constructor
Calling Copy Constructor
Calling Copy Constructor
Calling Copy Constructor
Calling Copy Constructor
receive called with 0x7fff15146ecf as parameter
Calling Copy Constructor
receive called with 0x7fff15146ecf as parameter
Calling Copy Constructor
receive called with 0x7fff15146ecf as parameter
但是通过指针传递并没有什么特别的魔力。所以它有原始答案中提到的问题,我将在下面保留。但事实证明,引用处理只是另一种野兽。
原始答案
是的,如果您的程序是多线程的,这可能会很危险。即使不是,它通常也是糟糕的风格。实际上,您应该通过信号和插槽连接按值传递对象。
请注意,Qt 支持“隐式共享类型”,因此“按值”传递诸如 QImage 之类的东西不会复制,除非有人写入他们收到的值:
http://qt-project.org/doc/qt-5/implicit-sharing.html
问题基本上与信号和插槽无关。 C++ 有各种方法可以删除对象,当它们在某处被引用时,或者即使它们的某些代码在调用堆栈中运行。在您无法控制代码并使用正确同步的任何代码中,您都可以很容易地遇到这个麻烦。使用 QSharedPointer 等技术会有所帮助。
Qt 提供了一些额外的有用的东西来更优雅地处理删除场景。如果你想销毁一个对象,但你知道它可能正在使用中,你可以使用 QObject::deleteLater() 方法:
http://qt-project.org/doc/qt-5/qobject.html#deleteLater
这对我来说已经派上用场了好几次了。另一个有用的是 QObject::destroyed() 信号:
http://qt-project.org/doc/qt-5/qobject.html#destroyed