Ответ 1
ОБНОВЛЕНИЕ 20-APR-2015
Первоначально я считал, что передача ссылки на объект, выделенный стеком, будет эквивалентна передаче адреса этого объекта. Следовательно, в отсутствие обертки, в которой будет храниться копия (или общий указатель), подключение к очередному слоту может завершиться с использованием плохих данных.
Но мое внимание привлекло @BenjaminT и @cgmb, что Qt действительно имеет специальную обработку для опорных параметров const. Он вызывается конструктором копирования и убирает скопированный объект для использования для вызовов слотов. Даже если исходный объект, который вы передали, был уничтожен к моменту запуска слота, ссылки, которые получают слоты, будут полностью связаны с различными объектами.
Вы можете прочитать @cgmb 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
Проблема не в корне не связана с сигналами и слотами. В С++ есть всевозможные способы удаления объектов, когда они упоминаются где-то, или даже если некоторые из их кода запущены в стеке вызовов. Вы можете легко справиться с этой проблемой в любом коде, где вы не имеете контроля над кодом и используете правильную синхронизацию. Техники, такие как использование QSharedPointer, могут помочь.
Есть несколько дополнительных полезных вещей, которые Qt предлагает более грациозно обрабатывать сценарии удаления. Если есть объект, который вы хотите уничтожить, но вы знаете, что он может быть использован в данный момент, вы можете использовать метод QObject:: deleteLater():
http://qt-project.org/doc/qt-5/qobject.html#deleteLater
Это пригодится мне пару раз. Еще одна полезная вещь - сигнал QObject:: destroy():