Russian Qt Forum

Qt => Общие вопросы => Тема начата: Eugene Efremov от Сентябрь 02, 2008, 02:12



Название: Связать число со слотом.
Отправлено: Eugene Efremov от Сентябрь 02, 2008, 02:12
Есть класс QSignalMapper, позволяющий превратить сигнал в строку или число. Как решать обратную задачу — связать слот с произвольной строкой или числом? В идеале хочется примерно такого:
Код:
enum MyEnum {Foo, Bar, Baz ....};
...

connect(this, Foo, other, SLOT(fooAction()));
...

void callSomeActions(MyEnum t)
{
    emit t;
}

Макросы  SIGNAL и SLOT преобразуют имя ф-ции именно в строку, так что сделать что-то похожее должно быть достаточно легко... Но сходу ответ найти не получилось...


P.S. Если не связываться с сигналами/слотами, то реализовать что-то похожее можно примерно так:

Код:
class caller_base
{
public:
virtual void call() = 0;
virtual ~caller_base() {}
};

template<class type> class caller : public caller_base
{
type *ptr;
void (type::*fun)();

public:
caller(type *p,  void (type::*f)()) : ptr(p), fun(f) {}

virtual void call()
{
(ptr->*fun)();
}
};

enum MyEnum {Foo, Bar, Baz};

QHash<int, caller_base *> hash;

template <class type>
void myConnect(MyEnum t, type *ptr, void (type::*fun)())
{
hash[t] = new caller<type>(ptr, fun);
}

void myEmit(int i)
{
if(hash.contains(i)) hash[i]->call();
}


Но раз уже существует готовый — и очень похожий — механизм, то соблазнительно использовать именно его, а не городить самодельные огороды...

P.P.S. Да, на всякий случай: switch/case не предлагать! :-)


Название: Re: Связать число со слотом.
Отправлено: Tonal от Сентябрь 02, 2008, 08:01
Ну а прчему переходник/диспетчер не сделать?
Который будет слушать интересующий сигнал, и дёргать подписанные объекты?
Можно и с применением шаблонов и без. :)


Название: Re: Связать число со слотом.
Отправлено: spirit от Сентябрь 02, 2008, 09:34
или же заюзать QSignalMapper.


Название: Re: Связать число со слотом.
Отправлено: Eugene Efremov от Сентябрь 02, 2008, 15:41
Ну а прчему переходник/диспетчер не сделать?
Который будет слушать интересующий сигнал, и дёргать подписанные объекты?
Не понял. Зачем мне слушать сигнал? Мне его сгенерить надо. На основе числа.

или же заюзать QSignalMapper.
Люди, вы читать умеете? Я же русским языком написал: требуется операция, обратная по отношению к той, которую обеспечивает QSignalMapper.
Не из сигнала получить число, а наоборот.


Название: Re: Связать число со слотом.
Отправлено: Alex03 от Сентябрь 03, 2008, 06:42
Давайте по порядку...
Вам надо:
1. именно генерить РАЗНЫЕ сигналы в зависимости от значения enum-а?
2. Вызывать определённые слоты определёных (заранее известных) объектов в зависимости от значения enum-а?
3. что то ещё?

Цитировать
P.P.S. Да, на всякий случай: switch/case не предлагать! :-)
Для первых 2-х случаев switch/case думаю будет оптимальным как по размеру кода дак и по скорости.
В случае enum-а без дырок можно ещё просто массив указателей на сигналы сделать... (впрочем оптимизирующие компиляторы тоже не дремлют и switch/case оптимизировать умеют, т.е. не делают длинных цепочек if ... else if ... else if ... else)

Ну и ИМХО шаблоны приведённые - хорошо, но несколько запутыват, для разных используемых типов в шаблоне генерится свой код функций и т.д.


Название: Re: Связать число со слотом.
Отправлено: ритт от Сентябрь 03, 2008, 06:55
храни в массиве или хэше сигнатуру сигнала и зови invokeMethod
рекомендую посмотреть исходники, генерируемые moc'ом


Название: Re: Связать число со слотом.
Отправлено: lit-uriy от Сентябрь 03, 2008, 15:14
может я чего-то не понял (всмысле исходную причину), но почему бы не посылать просто свой сигнал с параметром?
Просто число послать нельзя так как это не сигнал, даже если такое вообразить. потому-что не понятно когда это число посылать надо


Название: Re: Связать число со слотом.
Отправлено: Eugene Efremov от Сентябрь 03, 2008, 19:24
храни в массиве или хэше сигнатуру сигнала и зови invokeMethod
рекомендую посмотреть исходники, генерируемые moc'ом

О. Вот это именно то, что нужно. Спасибо!

Теперь попробую ответить тем, кто «что-то не понял»:
Задача простейшая и весьма часто встречающаяся в самых разных контекстах: требуется предпринимать разные действия в зависимости от различных значений некой величины. Недостатки традиционного решения через switch/case перечислять не буду: это и до меня делалось неоднократно, если кто все еще считает этот вариант наилучшим, чтож — его право. Я так не считаю.

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

Остается одно — хеш (в простейшем случае — массив), связывающий множество значений этой величины с множеством неких функторов. А касательно того, что это за функторы — тут возможны варианты... Простейший — с указателями на методы — имеет одну проблему: в языке не предусмотрено безопасное приведение (например, с помощью dynamic_cast) указателей на члены производных классов в указатели на члены базовых. Значит, если нам такое приведение понадобится (а оно понадобится) — придется извращаться.

Вариант с шаблонами, который я привел в первом постинге, решает эту проблему. И его хватает почти во всех случаев, если только нам не требуется масштабировать задачу на случай множества функторов с разным количеством и типом аргументов. Чтобы решить — если нужно —  эту проблему, уже придется пользоваться бустом или еще чем в том же роде...

Вот примерно так это разруливатеся. Если с решать с нуля.
А теперь обращаем внимание на то, что в Qt это все уже решено, причем для самого общего случая и в наиболее гибкой форме — в механизме слот-сигнального взаимодействия. И задается вопросом: а нельзя ли, вместо того, чтобы весь вышеописанный огород городить, заюзать для своих нужд этот — уже готовый и отлично работающий — механизм...

Вот об том этот тред и был. Спасибо за внимание, все свободны! ;)


Название: Re: Связать число со слотом.
Отправлено: ритт от Сентябрь 03, 2008, 19:40
нахожу последний пост несколько помпезным...

фактически invokeMethod использует кучу if и switch/case
разница с самопальной реализацией лишь в том, что в родной требуется только определить метаобъект, а самопальную нужно ещё придумать и написать :)


Название: Re: Связать число со слотом.
Отправлено: Eugene Efremov от Сентябрь 03, 2008, 20:48
фактически invokeMethod использует кучу if и switch/case

Хм... Посмотрел <qt>/src/corelib/kernel/qmetaobject.cpp — ни одного switch/case во всем файле.  :)
А что их moc использует — ну дык он робот, ему можно. ;)

в родной требуется только определить метаобъект, а самопальную нужно ещё придумать и написать :)

О том и речь...


Название: Re: Связать число со слотом.
Отправлено: ритт от Сентябрь 03, 2008, 23:39
рекомендую посмотреть исходники, генерируемые moc'ом

будем считать тему закрытой


Название: Re: Связать число со слотом.
Отправлено: Eugene Efremov от Сентябрь 04, 2008, 00:15
А что их moc использует — ну дык он робот, ему можно.


Название: Re: Связать число со слотом.
Отправлено: ритт от Сентябрь 04, 2008, 00:53
moc их не использует, moc их генерирует :)

иожно, конечно, и пофлудить...не забыть бы потом ветку подчистить


Название: Re: Связать число со слотом.
Отправлено: Alex03 от Сентябрь 04, 2008, 07:27
Не спора ради.

Теперь попробую ответить тем, кто «что-то не понял»:
Задача простейшая и весьма часто встречающаяся в самых разных контекстах: требуется предпринимать разные действия в зависимости от различных значений некой величины. Недостатки традиционного решения через switch/case перечислять не буду: это и до меня делалось неоднократно, если кто все еще считает этот вариант наилучшим, чтож — его право. Я так не считаю.
Эт видимо камень в мой огород. Я не считаю switch/case наилучшим во всех случаях, но в ряде случаев применение этой конструкции вполне оправдано. В частности для исходного примера с enum MyEnum {Foo, Bar, Baz ....}; т.е. тогда когда "некая величина" принимает конечное, заранее известное, и, желательно небольшое количество значений.

В ряде других случаев, в том числе когда нужно динамическое назначение неких функторов для некой величины - да switch/case не катит. Но этих случаев гораздо меньше.


invokeMethod() тут тоже не самый лучший (оптимальный по производительности) вариант ибо работает со строковыми литералами, генерит строку типа "methodName(type1,type2)", получает из неё индекс метода (QMetaObject::indexOfMethod()) простым банальным strcmp() в циклах по именам методов (сигналов и слотов) объектов в иерархии, и в конце концов вызывает qt_metacall() (в случае с Qt::DirectConnection)
Т.е. было бы оптимальней хранить в хэше не сигнатуру сигнала/слота (в обшем случае метода), а индекс метода (QMetaObject::indexOfMethod()) и вызывать qt_metacall() напрямую, вот только qt_metacall() не документирован у троллей, и вполне могут чтото поменят в будущем. :(


PS А недостатки switch/case не плохо было бы послушать, вдруг чего не знаю. Серьёзно.


Название: Re: Связать число со слотом.
Отправлено: Tonal от Сентябрь 04, 2008, 08:57
Я использую хеш с функторами, если разных значений >2 но мне это проще, т.к. я пишу в основном на python-е, а там и функтор и хеш в язык встроены.
Т.е. используешь не задумываясь. :)

Хотя таких ситуаций у меня довольно мало...


Название: Re: Связать число со слотом.
Отправлено: Eugene Efremov от Сентябрь 04, 2008, 09:49
Я не считаю switch/case наилучшим во всех случаях, но в ряде случаев применение этой конструкции вполне оправдано. В частности для исходного примера с enum MyEnum {Foo, Bar, Baz ....}; т.е. тогда когда "некая величина" принимает конечное, заранее известное, и, желательно небольшое количество значений.

У этой конструкции есть очень нехорошее свойство: появившись в одной точке программы оно стремится расползтись и продублировать себя везде, где только можно. За примерами далеко ходить не надо: возьмем, к примеру,  QAbstractItemModel и посчитаем все методы в которых фигурирует role. Тривиальные реализации этих методов потребуют, скорее всего, вложенного switch/case: для role и для индекса. Одинакового для них для всех.

Так что, если его использовать, надо принять меры, чтобы само перечисление не вылезло за пределы того метода, где оно используется. Иначе рано или поздно придется применить эту конструкцию во второй раз. И в третий. И в четвертый. А это — дублирование кода, со всеми вытекающими...

Ну и еще его, конечно, можно использовать при автоматической генерации кода. Роботу дублирование не страшно...

Т.е. было бы оптимальней хранить в хэше не сигнатуру сигнала/слота (в обшем случае метода), а индекс метода (QMetaObject::indexOfMethod()) и вызывать qt_metacall() напрямую, вот только qt_metacall() не документирован у троллей, и вполне могут чтото поменят в будущем. :(

Мда. Поиграл с этим, получилось примерно вот что:

Код:
class Caller : public QObject
{
Q_OBJECT

private:
QHash<int, int> ftrs;

protected:
bool add(int i, const char *slot) // use: add(i, SLOT(foo()))
{
int id = metaObject()->indexOfMethod(QMetaObject::normalizedSignature(slot+1));
if(id == -1) return false;
ftrs[i] =  id;
return true;
}

virtual void defMethod() {}

public:
Caller(QObject *p = 0): QObject(p) {}

bool call(int i)
{
if(ftrs.contains(i))
{
qt_metacall(QMetaObject::InvokeMetaMethod, ftrs[i], 0);
return true;
}
defMethod();
return false;
}

};
И это еще без передачи аргументов. Под 4.4.1 работает. Будет ли работать под другими версиями?...

P.S. Я тут на неделю уезжаю, так что с дискуссией пока придется завязать...


Название: Re: Связать число со слотом.
Отправлено: ритт от Сентябрь 04, 2008, 10:40
работать будет, но чисто теоритически индекс метода у суперкласса может отличаться от индекса того же метода у наследника. вдобавок у суперкласса может вообще не оказаться метода под таким индексом, а у другого наследника под тем же индексом будет совершенно другой метод
тогда придётся везде расставлять дополнительные проверки или работать только с суперклассами

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


Название: Re: Связать число со слотом.
Отправлено: Alex03 от Сентябрь 04, 2008, 11:51
Т.е. было бы оптимальней хранить в хэше не сигнатуру сигнала/слота (в обшем случае метода), а индекс метода (QMetaObject::indexOfMethod()) и вызывать qt_metacall() напрямую, вот только qt_metacall() не документирован у троллей, и вполне могут чтото поменят в будущем. :(

Мда. Поиграл с этим, получилось примерно вот что:
...
Можно расширить для вызова членов других объектов, например в хэше хранить структурку с указателем на объект и индексом метода этого объекта. Но тут надо гарантировать то, что объект не будет уничтожен пока ссылка на него имеется в хэше, ну или отлавливать QObject::destroyed() этих объектов (или в конце концов просто QPointer задействовать).
Второй вариант расширения функциональности - Хранить в хэше не структурку, а QList со структурками, дабы на одно "некое значение" можно было "коннектить" несколько сигналов/слотов.
После этих изменений Caller (иль например SlotMapper) можно использовать везде где угодно как член класса.

Цитировать
И это еще без передачи аргументов.
А какие тут аргументы имеются ввиду? Чтото у меня реальных примеров не придумывается...

Цитировать
Под 4.4.1 работает. Будет ли работать под другими версиями?...
Думаю под всеми 4.х.х должно работать.

Константин Так как индекс задаётся не статически, а получается из сигнатуры с помощью metaObject()->indexOfMethod() то всё ОК.
Собственно connect() также получает индексы сендера и ресивера по сигнатуре и запоминаются именно индексы. А уже activate() по индексу сигнала находит все ресиверы с соотв. индексами и вызывает их прямо или через очередь.


Название: Re: Связать число со слотом.
Отправлено: Alex03 от Сентябрь 04, 2008, 12:00
Странно что тролли хранят сигнатуры а не хэши сигнатур, их то можно было бы MOC-ом сгенерить
QMetaObject::indexOfХХХХХ() отрабатывали бы куда быстрее...
А следовательно и connect() и invokeMethod() работали бы быстрее...


Название: Re: Связать число со слотом.
Отправлено: pastor от Сентябрь 04, 2008, 12:03
Странно что тролли хранят сигнатуры а не хэши сигнатур, их то можно было бы MOC-ом сгенерить
QMetaObject::indexOfХХХХХ() отрабатывали бы куда быстрее...
А следовательно и connect() и invokeMethod() работали бы быстрее...


имхо этими мыслями можно поделиться с тролями.Накатай suggestion им


Название: Re: Связать число со слотом.
Отправлено: ритт от Сентябрь 04, 2008, 12:16
Так как индекс задаётся не статически, а получается из сигнатуры с помощью metaObject()->indexOfMethod() то всё ОК.
Собственно connect() также получает индексы сендера и ресивера по сигнатуре и запоминаются именно индексы. А уже activate() по индексу сигнала находит все ресиверы с соотв. индексами и вызывает их прямо или через очередь.

тогда придётся хранить индекс и указатель на объект (либо метаобъект)
коннекту позволительно оперировать с индексами - он уже все проверки сигнатур выполнил, указатель на объект имеет...а мок всё-равно понагенерил кода с использованием индексов...


Название: Re: Связать число со слотом.
Отправлено: Alex03 от Сентябрь 04, 2008, 12:32
тогда придётся хранить индекс и указатель на объект
Про то и речь, с сигнатурой тоже указатель на объект нужен...


Название: Re: Связать число со слотом.
Отправлено: ритт от Сентябрь 04, 2008, 13:47
уточню свою мысль:
чтобы узнать индекс метода, нужен указатель на объект (или метаобъект, или хардкодить на статический метаобъект класса) - лишь после этого можно утверждать, что индекс валиден;
в ситуации с сигнатурой указатель на объект потребуется лишь в момент получения индекса метода по сигнатуре и коннекте/вызове метода, а саму сигнатуру можно получать от третьей стороны динамически (скажем, из плагина или из текстового файла - без примеров, просто абстракция), что нереализуемо на индексах (не будет гарантии работоспосоности при изменениях)

в случае с индексами всё-равно придётся получать сигнатуру (проверять по ней параметры, делать инвок, коннект и т.д.) и валиден индекс будет ровно до первого изменения указателя на объект (затем валидность не гарантируется и нужно выполнять проверки снова)
в случае с сигнатурами индекс потребуется только для работы с метаметодом - так что, сигнатуру можно сразу использовать при вызове коннект/инвок /* только желательно бы сигнатуру нормализовать сразу при получении */
в итоге можно ничего не хардкодить и смело строить дерево действий, зависящих от значения переменной (тема треда), хоть из текстового файла, а скорость отработки будет выше

мне не так давно пришлось любиться с системой метаобъектов...вышесказанное - всего лишь теория, подтверждённая практикой :)


Название: Re: Связать число со слотом.
Отправлено: Alex03 от Сентябрь 05, 2008, 05:43
Константин Вроде про динамическое изменение указателей на объект для одной и той же сигнатуры речь не шла.
Впрочем ничто не мешает получить индекс при каждом изменении указателя. Ведь в большинстве случаем количество изменений(установок) указателя намного меньше количества вызовов слота/сигнала.


Название: Re: Связать число со слотом.
Отправлено: ритт от Сентябрь 05, 2008, 10:45
Впрочем ничто не мешает получить индекс при каждом изменении указателя. Ведь в большинстве случаем количество изменений(установок) указателя намного меньше количества вызовов слота/сигнала.
ну, а что потом будем делать с этим индексом? :)
в инвок индекс на запихать - всё-равно потребуется брать сигнатуру - и в чём тогда смысл хранения индексов?


Название: Re: Связать число со слотом.
Отправлено: ритт от Сентябрь 05, 2008, 11:05
кстати, в 4.5.0 был перегруженный инвок, принимающий индекс метода, но вчера весь этот код искоренили - наверное, на то есть причины


Название: Re: Связать число со слотом.
Отправлено: Alex03 от Сентябрь 07, 2008, 12:17
кстати, в 4.5.0 был перегруженный инвок, принимающий индекс метода, но вчера весь этот код искоренили - наверное, на то есть причины
А qt_metacall() случайно не документировали?

С другой стороны понятно, что пока внутренности реализации сигналов/слотов "спрятаны" троли оставляют за собой право менять там что угодно.


Название: Re: Связать число со слотом.
Отправлено: ритт от Сентябрь 07, 2008, 15:28
А qt_metacall() случайно не документировали?

есть несколько строк комментов в коде :)
плюсом добавили internal-only статический qt_metacall(), который использует новый механизм "метаконструктор" для создания экземпляра и вызова метода...но это скорее больше скриптам полезно, чем данной теме :)