LINUX.ORG.RU

Сигналы и передача по ссылке

 ,


1

3

Вопрос такой: есть ли гарантия, что если сигнал вызывается с ссылкой на обьект а слот тоже принимает ссылку, то сам обьект не будет копирован где-нибудь посередине?

Т.е. пример кода будет выглядеть так:

struct Foo { char data[500]; }

class Emitter : public QObject {
    Q_OBJECT
signals:
    void makeFoo() { 
        foo.emplace_back(); 
        emit fooCreated(foo.back());
    }

    void fooCreated(Foo &);
private:
   std::vector<Foo> foo;
};

class Receiver : public QObject {
    Q_OBJECT
public slots:
    void onFooCreated(const Foo &) {}
};

void run() {
    Emmiter e;
    Receiver r;
    QObject::connect(&e, &Emitter::fooCreated, 
                     &r, &Receiver::onFooCreated);

    e.makeFoo(); 
}

А вопрос для данног фрагмента кода: есть ли гарантии, что Foo не будет скопирован? (зависит ли копирование от типа соединения в QObject::connect)

★★★★★

Последнее исправление: KennyMinigun (всего исправлений: 1)

Насколько я знаю, QueuedConnection всегда копирует.

А сигналы всегда определяются как ссылки, поэтому писать Foo& смысла нет.

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon

Имеет, если писать без ссылок, то копирований будет несколько. По крайней мере так было, когда я это тестировал.

equeim ★★
()

Если тип соединения DirectConnection, либо AutoConnection и QObject'ы привязаны к одному треду, то аргумент передастся по ссылке. Если соединение QueuedConnection, либо AutoConnection и QObject'ы привязаны к разным тредам, то скопируется.

equeim ★★
()
Последнее исправление: equeim (всего исправлений: 1)
Ответ на: комментарий от equeim

Да. ТС'у нужно создать фантомный класс с логированием копирования и всё станет на свои места.

RazrFalcon ★★★★★
()
Ответ на: комментарий от equeim

Проще говоря:

DirectConnection - аргумент передается по ссылке (но только если в определении и сигнала, и слота параметр написан в виде ссылки. Иначе скопируется), слот вызывается в треде того, кто вызвал сигнал.

QueuedConnection - аргумент копируется (если в определении сигнала либо слота параметр передается по значению, то копирований будет несколько), слот вызывается в треде получателя.

AutoConnection (дефолт) - автоматически выбирает DirectConnection либо QueuedConnection во время вызова сигнала в зависимости от того, принадлежат ли оба объекта к одному треду.

Кстати говоря, в Qt5 слоты не обязательно помечать как slots. Это нужно только для рефлексии (либо что вызывать их из QML). Сигналы будут работать точно также и без этого.

equeim ★★
()
Последнее исправление: equeim (всего исправлений: 1)

Есть std::reference_wrapper, на колене такое тоже не долго пишется.

Но, за время жизни обьекта тут уж ты сам отвечаешь в случае того же QueuedConnection. Как этот случай обрабатывается в Qt, я не уверен, но скорее всего, там не стали оборачивать ссылки, что бы многопоточность совсем уж не выносила мозг.

pon4ik ★★★★★
()

Про копирование здесь. Полезно почитать предыдущие 2 части.

fluorite ★★★★★
()
Ответ на: комментарий от equeim

Спасибо​ большое! Как раз ответ на мой вопрос

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от equeim

Кстати говоря, в Qt5 слоты не обязательно помечать как slots.

вообще, если с++ классу не реализовать метод и не трогать его, прога скомпилится под linux и будет даже работать.

а вот если такой метод оставить после slots: то будет undefined symbol. qt creator даже покажет где конкретно в сгенеронном mocом коде его дернули.

qt5 другое дает - связывать слот с указателем на метод или лямбдой

ckotinko ☆☆☆
()
Ответ на: комментарий от ckotinko

Да, лямбда помогает в ситуациях когда нужно обойти вызов QObject::sender

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от tyro33

Как вариант. Память под указателем уже надо более явно копировать.

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от tyro33

Если передаются контейнеры/строки, то контейнеры из состава Qt являются copy-on-write, т.е. сами при копировании контейнера сами данные не копируются. В том числе и QByteArray (если нужно передать содержимое файла, например).

equeim ★★
()
Ответ на: комментарий от RazrFalcon

Clang кстати тоже выпиливает ненужные методы: https://wandbox.org/permlink/OF1d2zOoyrX6iq6U

А что, в VS под оффтопиком таки падает компиляция?

UPD Проверил, Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23506 for x86 тоже нормально компилирует.

KennyMinigun ★★★★★
() автор топика
Последнее исправление: KennyMinigun (всего исправлений: 2)
Ответ на: комментарий от ckotinko

Да не вопрос. Но в отличии от ссылок, передать указатель на Qt контейнер в слот просто так не получится. Нужно регистрировать тип через qRegisterMetaType. А в какой-то из 4 версий даже это не работало

tyro33
()
Ответ на: комментарий от tyro33

передать указатель на Qt контейнер в слот просто так не получится

вас кто-то обманул.

qRegisterMetaType нужен только если необходимо копировать аргументы нетривиальным образом. указатели копируются тривиально.

ckotinko ☆☆☆
()
Ответ на: комментарий от deep-purple

смотри как это сделано внутри:

ты пишешь c одной стороны сигнал

void mysig(void * arg1, int arg2, double arg3)
с другой слот
void myslot(void * arg1, int arg2)
как происходит их соединение?

в общем случае, аргументы копируются, то есть можешь считать, что создается вот такая структура(в реальности немного не так, но пофиг)

struct args_for_signal_mysig : some_common_struct_for_sigs {
   void * arg1;
   int      arg2;
   double arg3;

   void * pargs[3];
};
и создается:
void mysig(void * arg1, int arg2, double arg3) {
   void * args = new args_for_signam_mysig;
   args->arg1 = arg1;
   args->arg2 = arg2;
   args->arg3 = arg3;
вот где твои параметры будут копироваться
   args->pargs[0] = &args->arg1;
   args->pargs[1] = &args->arg2;
   args->pargs[2] = &args->arg3;
   send_signal_somehow(args, args->pargs, 3);
в реализации mysig идет проверка, что у сигнала нет отложеных (queued) получателей. если их нет, то массив указателей соберут прямо из аргументов, минуя копирование. А на принимающей,
void myclass::dispatch_signal(QObject * sender, int signal_id, void ** pargs, int count) {
вот этот pargs - он как раз массив ранее заполненых аргументов. в сгенереном mocом файле будет switch по signal_id, а в нем, будут вызываться конкрентые функции, которым на вход будет подаваться что-то то такое:
case 100500:
   if (count >= 2)
      this->myslot(*reinterpret_cast<void*>(pargs[0]), 
                          *reinterpret_cast<int*>(pargs[1]));

то есть любой аргумент сперва копируется как есть, по правилам С++, затем берется его адрес и кастуется к void*, и в составе массива уходит получателям. получатели же берут этот массив и раскастовывают указатели обратно к нужным типам, дереференсят и получают нужные типы. поэтому типы аргументов должны совпадать.

конкретно в твоем случае если соединение только прямое, то этап копирования в самом сигнале будет пропущен и массив указателей соберут прямо из аргументов. если соединение не прямое, то копирование произойдет, при этом QString& будет низведен до QString, который станет копией и именно на него будет передан указатель.

указатель же на ссылку = ссылке. они в принципе одно и то же только по разному пишутся.

ckotinko ☆☆☆
()
Последнее исправление: ckotinko (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.