Pull to refresh

Comments 17

Не могу пока проверить в деле, но мне кажется будут проблемы, если в параметрах сигналов есть ссылочные типы.
Хранить их в QList<std::tuple<ParamT...> > наверное нельзя.
От проблем можно уйти с помощью std::decay

QList<std::tuple<typename std::decay<ParamT>::type...> >
Об этом я не подумал. Но только что проверил дома — работает.
А с советом от Lol4t0 про decay — должно вообще на 110 процентов работать.
Оно компилируется, но можно попасть на битые ссылки:
вот так
Да, но тут дело в том, что значения успевают скопироваться в контейнер до того как выходят из поля зрения.
Следующий код работает:
auto spy = createSignalSpy(&signalSource,  &SignalClass::someSignal);
    {
        double a = 3.2;
        int b = 4;
        signalSource.f(a, b);
    }
    std::cout<<get<0>(spy.calls.front())<<"\n";
    std::cout<<get<1>(spy.calls.front())<<"\n";

Выдаёт:
3.2
4
Так контейнер тоже хранит ссылку на переменную, а не значение переменной. Битая ссылка — это undefined behavior, поэтому может казаться, что все работает, как надо, в то время как на самом деле все плохо.

Грубо говоря, у вас может получиться

QList<std::tuple<const sttring&> > m_calls;
                             ^^^ ссылка


Для того и нужен decay, чтобы отбросить ссылку в контейнере
Мне кажется проблему с ссылками в общем виде не решить.
Либо могут быть битые ссылки, как вы указали, что может быть актуально и при передаче указателей.
Либо, при использовании decay, не будет работать с не-копируемыми объектами (например QObject&).

Думаю первый вариант достаточно безопасный.
часто ли QObject передается по ссылке? Обычно по указателю.

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

Если человек передаёт указатель, то он вполне может ожидать, что объект, на который указатель указывает, не будет скопирован. А для ссылок это поведение не естественно, этого никто не ожидает. А код должен вести себя предсказуемо, тем более что ваш класс будут использовать с новым, не проверенным кодом. В таком случае ошибки, скорее всего, будут искать совсем не в том месте.
часто ли QObject передается по ссылке? Обычно по указателю.

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

С некопируемыми объектами оно не будет работать явно

То есть для некоторых случаев пользоваться этим средством SignalSpy нельзя в принципе.

а с битами ссылками оно будет работать непредсказуемо. Первый вариант гораздо лучше.

С битыми ссылками оно будет работать так же, как и с битыми указателями. Проблему битых указателей не решить.

Если человек передаёт указатель, то он вполне может ожидать, что объект, на который указатель указывает, не будет скопирован. А для ссылок это поведение не естественно, этого никто не ожидает. А код должен вести себя предсказуемо, тем более что ваш класс будут использовать с новым, не проверенным кодом. В таком случае ошибки, скорее всего, будут искать совсем не в том месте.

У меня немного другая логика. Если объект нельзя копировать — это выражается в С++ запретом оператора и конструктора копирования и вы не сможете случайно его скопировать. Если объект почти никогда нельзя копировать (например очень тяжелый), но в редких случаях нужно, то делают специальные функции или методы, которые этим занимаются и «случайно» их вызвать нельзя (Clone, Copy). Ссылки в С++ дают чуть больше выразительности, по сравнению с указателями из С.

Думаю нужно просто сделать два варианта SignalSpy — по умолчанию c decay и специальная версия без decay для «клинических» случаев с cсылками.
Ссылки и указатели — семантически очень разные вещи. Паттерны их использования довольно сильно отличаются. Начнем с того, что ссылка никогда не может передана вам во владение, в отличие от указателя, владелец которого обычно неявно регламентируется используемым шаблоном. То есть при возврате указателя из фабрики всегда предполагается, что объект отдается вам во владение, например. А в хорошем современном C++ сырые указатели — это почти всегда данные, которые сохранять нельзя. Иначе вам отдадут вместо этого умный указатель.

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

Ссылки на l-value можно сохранять только в том случае, если вы уверены, что объекты, на которые они ссылаются, переживут ваш объект. Например, если вы пишете mutex locker, то он явно переживет mutex, который блокирует.

Если брать Qt подход, то у них ссылки не сохраняются в принципе, если передается что-то, что можно сохранять, то передается указатель (посмотрите mutexlocker, filestream, например)
Совершенно согласен.
Вопрос в другом — как сделать SignalSpy, который сможет тестировать слоты, которые передают в параметре объект, не предназначенный для последующего сохранения (по ссылке).
Либо мы говорим, что по ссылке передавать значения в сигналах это плохой тон и закрываем тему: о).
вообще говоря, в сигналах можно передавать только копируемые объекты, потому что в противном случае не будет работать QueuedConnection.

При передачи ссылок в сигналах надо иметь в виду, что Qt всегда может скопировать объект при QueuedConnection. А тип соединения вы, при разработке кода не контролируете, потому что соединение выполняет клиентский по отношению к объекту код.

Так что я думаю, хуже не будет, если копировать. Можно только еще одну ошибку предупредить.
C QueuedConnection — это в точку.
Там и с указателями осторожно надо работать. Значит будем работать без ссылок ;o).
Если брать Qt подход, то у них ссылки не сохраняются в принципе, если передается что-то, что можно сохранять, то передается указатель (посмотрите mutexlocker, filestream, например)

Кстати, посмотрел аналог mutexlocker из STL — std::lock_guard. У разработчиков стандарта подход, отличный от разработчиков Qt, и они передают в конструкторе ссылку и запоминают её.
У вас в последнем листинге в «фабрике» присутствует некий Type, который нигде не объявлен, видимо вы имели в виду T.

Я бы для типа указателя и типа в указателе на функцию-член ввёл разные параметры, чтобы позволить такие случаи, когда сигнал объявлен в базовом классе, а передаётся указатель на производный класс. В вашем коде для такого случая будет ошибка компиляции, так как для T будут выведены разные типы. Если очень хочется внятное сообщение об ошибке при передаче параметров несвязанных типов, можно использовать std::is_base_of и static_assert.
Да, Вы правы, так и есть. Но если мы используем сигнал базового класса, а в фабрику передаем указатель на наследника, то можно просто поступить так:
auto spy = createSignalSpy(static_cast<Base*>(&derivedObject), &Base::signalInBase).

п.с спасибо за указание на опечатку — исправил
Можно конечно, но ведь куда проще задействовать неявное преобразование к базовому? :-)
Sign up to leave a comment.

Articles