сигнал и параметр Qt объекта стека в качестве эталона - PullRequest
25 голосов
/ 10 декабря 2011

Могу ли я получить "свисающую ссылку" со следующим кодом (в конечном слоте, подключенном к myQtSignal)?

class Test : public QObject
{
    Q_OBJECT

signals:
    void myQtSignal(const FooObject& obj);

public:
    void sendSignal(const FooObject& fooStackObject)
    {
        emit  myQtSignal(fooStackObject);
    }
};

void f()
{
    FooObject fooStackObject;
    Test t;
    t.sendSignal(fooStackObject);
}

int main()
{
    f();
    std::cin.ignore();
    return 0;
}

Особенно, если emit и slot не выполняются в одном потоке.

Ответы [ 4 ]

29 голосов
/ 10 декабря 2011

ОБНОВЛЕНИЕ 20-АПРЕЛЯ 2015

Первоначально Я полагал, что передача ссылки на объект, выделенный из стека, будет эквивалентна передаче адреса этого объекта. Следовательно, в отсутствие оболочки, в которой будет храниться копия (или общий указатель) , соединение в слот с очередями может завершиться с использованием неверных данных.

Но @BenjaminT и @cgmb обратили мое внимание на то, что Qt действительно имеет специальную обработку для константных ссылочных параметров. Он вызовет конструктор копирования и уберет скопированный объект для использования в вызовах слотов. Даже если исходный объект, который вы передали, был уничтожен к моменту запуска слота, ссылки, которые получат слоты, будут полностью относиться к разным объектам.

Вы можете прочитать @ cgmb's ответ для получения подробных сведений о механике. Но вот быстрый тест:

#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 / док / кварта-5 / неявная sharing.html

Проблема принципиально не связана с сигналами и слотами. В C ++ есть разные способы удаления объектов, когда на них есть ссылки, или даже если какой-то их код выполняется в стеке вызовов. Вы можете легко попасть в эту проблему в любом коде, где у вас нет контроля над кодом и используется правильная синхронизация. Такие методы, как использование QSharedPointer, могут помочь.

Есть несколько дополнительных полезных вещей, которые Qt предлагает для более изящной обработки сценариев удаления. Если есть объект, который вы хотите уничтожить, но вы знаете, что он может использоваться в данный момент, вы можете использовать метод QObject :: deleteLater ():

http://qt -project.org / док / кварта-5 / qobject.html # deleteLater

Это пригодилось мне пару раз. Еще одна полезная вещь - сигнал QObject :: destroy ():

http://qt -project.org / док / кварты-5 / qobject.html # уничтожены

23 голосов
/ 09 августа 2013

Извините, что продолжаю тему лет, но она появилась в Google.Я хочу уточнить ответ HostileFork, так как он может ввести в заблуждение будущих читателей.

Передача ссылки на сигнал Qt не опасна благодаря тому, как работают соединения сигнал / слот:

  • Еслисоединение прямое, подключенные слоты напрямую вызываются напрямую, например, когда emit MySignal(my_string) возвращает все выполненные напрямую подключенные слоты.
  • Если соединение ставится в очередь, Qt создает копию ссылок .Поэтому, когда слот вызывается, он имеет свою собственную действительную копию переменных, переданных по ссылке.Однако это означает, что параметры должны быть того типа, о котором Qt знает, чтобы скопировать его.

http://qt -project.org / doc / qt-5.1 / qtcore / qt.HTML # ConnectionType-перечисление

14 голосов
/ 20 апреля 2015

Нет, вы не встретите свисающую ссылку. По крайней мере, если ваш слот не выполняет такие функции, которые могут вызвать проблемы в обычных функциях.

Qt :: DirectionConnection

Обычно мы можем согласиться с тем, что это не будет проблемой для прямых соединений, так как эти слоты вызываются немедленно. Ваш сигнал излучается до тех пор, пока не будут вызваны все слоты. Как только это произойдет, emit myQtSignal(fooStackObject); вернется как обычная функция. На самом деле, myQtSignal(fooStackObject); - это обычная функция! Ключевое слово emit полностью для вашей выгоды - оно ничего не делает. Сигнальная функция просто особенная, потому что ее код генерируется компилятором Qt: moc .

Qt :: QueuedConnection

Бенджамин Т указал в документации, что аргументы копируются, но я думаю, что полезно изучить, как это работает под капотом (по крайней мере, в Qt 4).

Если мы начнем с компиляции нашего проекта и поиска нашего сгенерированного файла moc, мы можем найти что-то вроде этого:

// SIGNAL 0
void Test::myQtSignal(const FooObject & _t1)
{
    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

Итак, в основном, мы передаем QMetaObject::activate несколько вещей: наш QObject, метаобъект для типа нашего QObject, наш идентификатор сигнала и указатель на каждый из аргументов, которые получил наш сигнал.

Если мы исследуем QMetaObject::activate, мы обнаружим, что оно объявлено в qobject.cpp . Это неотъемлемая часть работы QObjects. После просмотра некоторых вещей, которые не имеют отношения к этому вопросу, мы находим поведение для подключений в очереди. На этот раз мы вызываем QMetaObject::queued_activate с нашим QObject, индексом сигнала, объектом, представляющим соединение от сигнала к слоту, и снова аргументами.

if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
    || (c->connectionType == Qt::QueuedConnection)) {
    queued_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv);
    continue;

Достигнув queued_activate, мы наконец-то добрались до сути вопроса.

Сначала он формирует список типов соединений из сигнала:

QMetaMethod m = sender->metaObject()->method(signal);
int *tmp = queuedConnectionTypes(m.parameterTypes());

Важным в queuedConnectionTypes является то, что он использует QMetaType::type(const char* typeName), чтобы получить идентификатор метатипа типа аргумента из сигнатуры сигнала. Это означает две вещи:

  1. Тип должен иметь идентификатор QMetaType, поэтому он должен быть зарегистрирован с qRegisterMetaType.

  2. Типы нормализованы . Это означает, что «const T &» и «T» отображаются на идентификатор QMetaType для T.

Наконец, queued_activate передает типы аргументов сигнала и заданные аргументы сигнала в QMetaType::construct, чтобы копировать-создавать новые объекты со временем жизни, которое будет длиться до тех пор, пока слот не будет вызван в другом потоке. Как только событие было поставлено в очередь, сигнал возвращается.

И это в основном история.

0 голосов
/ 10 декабря 2011

Если область действия, в которой существует объект, заканчивается и затем используется, он будет ссылаться на уничтоженный объект, что приведет к неопределенному поведению.Если вы не уверены, закончится ли область действия, лучше всего выделить объект в бесплатном хранилище через new и использовать что-то вроде shared_ptr для управления временем его жизни.

...