![]() |
Модераторы: Daevaorn |
![]() ![]() ![]() |
|
ZeusAtVingrad |
|
||||
Шустрый ![]() Профиль Группа: Участник Сообщений: 82 Регистрация: 12.4.2006 Репутация: нет Всего: нет |
Добрый день!
Передо мной встала такая задача (или по-крайней мере у меня сложился такой путь её решения): Есть базовый класс. Известно, что у его наследников будут функции с определёнными (одинаковыми) сигнатурами, но неизвестно сколько их будет и как они будут называться. Т.е. в базовый класс их не пропишешь ни под каким видом. В то же время, код, использующий объекты производных классов, ни о чём кроме базового класса не знает. Я объявил typedef с указателем на функцию-член БАЗОВОГО класса (с нужной мне сигнатурой - в примере void). А затем, получая указатели на функции-члены ПРОИЗВОДНЫХ классов и применяя их к указателям на объекты ПРОИЗВОДНЫХ классов - вызываю функции этих производных классов. Погонял код и так, и сяк. И с виртуальными функциями и с наследованием следующего уровня - вроде бы всё работает как задумано. В рабочей версии производные классы будут лежать в shared library, грубо говоря - вызывающий код будет получать всё: и указатель на объект и указатель на функцию - имея "на руках" только строки с названиями *.so (*.dll) и функций. Вопросы: 1. Это у меня случайно работает или всё соответствует стандартам языка и будет работать и на других платформах, на других компиляторах? 2. Вообще задумка здравая или по фен-шую правильнее было бы решать эту задачу как-то по-другому?
Выводит (как я и задумывал) следующее:
Это сообщение отредактировал(а) ZeusAtVingrad - 21.11.2013, 22:43 |
||||
|
|||||
vinter |
|
|||
![]() Explorer ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2735 Регистрация: 1.4.2006 Где: Н.Новгород Репутация: 13 Всего: 56 |
Вообще говоря если соблюдать осторожность и использовать правильные функции на правильных объектах, то да, этот код должен работать на любой платформе. Но это подходит для домашних проектов, т.к. когда над проектом работают больше одного человека, то об осторожности можно забыть. Любое решение должно быть устойчивым, это решение абсолютно неустойчиво. Можно присвоить функцию из одного объекта - другому и тогда будет UB.
Если у тебя появилось желание достучаться из предка к потомку, то значит у тебя что-то не так с архитектурой. |
|||
|
||||
ZeusAtVingrad |
|
|||
Шустрый ![]() Профиль Группа: Участник Сообщений: 82 Регистрация: 12.4.2006 Репутация: нет Всего: нет |
А если всё это завернуть в архитектуру так, чтобы программист не мог вызвать не ту функцию у не того объекта? Ограничить возможности программистам по оперированию указателями (в идеале - чтобы они этих указателей и не видели).
А как по другому можно описать множество объектов про которых известно только то, что у них есть функции с определённой сигнатурой? |
|||
|
||||
vinter |
|
|||
![]() Explorer ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2735 Регистрация: 1.4.2006 Где: Н.Новгород Репутация: 13 Всего: 56 |
ZeusAtVingrad, ты попробуй описать конкретную задачу. Быть может ты вообще не туда копаешь.
|
|||
|
||||
ZeusAtVingrad |
|
|||
Шустрый ![]() Профиль Группа: Участник Сообщений: 82 Регистрация: 12.4.2006 Репутация: нет Всего: нет |
Нужен "компот" из компонентов, подгружаемых "движком" из расширяемых библиотек в соответствии с конфигом. У компонентов - набор свойств (функции set/get).
Движок о них ничего не знает. Между свойствами устанавливаются связи-подписки: изменение свойства одного компонента рассылается в свойства других компонентов. В целом, задача стоИт такая. |
|||
|
||||
vinter |
|
|||
![]() Explorer ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2735 Регистрация: 1.4.2006 Где: Н.Новгород Репутация: 13 Всего: 56 |
я всё равно ничего не понял. Нужно подробное описание, чтобы можно было что-то нормальное подсказать.
Но если надо вызывать метода по какому-то событию, то можно храить контейнер из std::function<void()>, а в контейнер напихивать нужные функции через std::bind. Если нет возможности использовать современный компилятор, то можно заменить std на boost и использовать его. Так ты избавишься от проблем с неправильным присваиванием функций. Каждая функция будет связан с правильным объектом. |
|||
|
||||
bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 63 Всего: 196 |
ZeusAtVingrad, в этом случае обычно используется механизм подобный следующему: Базовый класс содержит контейнер свойств (std::map<const char*,std::pair<Getter,Setter> >), в который каждый потомок из конструктора добавляет свои свойства, а пользователь класса использует специальные методы базового класса для задания значений свойств.
|
|||
|
||||
ZeusAtVingrad |
|
||||||||
Шустрый ![]() Профиль Группа: Участник Сообщений: 82 Регистрация: 12.4.2006 Репутация: нет Всего: нет |
Нужна поддержка примерно следующего (очень простой и короткий пример, для иллюстрации):
Все объекты лежат в некоем контейнере. Работают самостоятельно (у кого-то поток слушает сеть, у кого-то поток с таймером "щёлкает", кто-то получает события с графики), передают значения друг другу и всё это - работающее приложение.
Спасибо, в сторону bind'а посмотрю. Не хотелось решения типа boost::any (из-за скорости), но похоже, что всё-равно придётся использовать (и) его.
Была такая мысль. Получается, что в каждом объекте класса-наследника будет такой map? Нерационально с точки зрения использования памяти. И static его не сделаешь. Потому что для классов-наследников они всё-таки должны быть разными. И ещё: Getter и Setter - что из себя могут представлять? Опять в каком-то виде - указатель на объект и на функцию? |
||||||||
|
|||||||||
bsa |
|
||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 63 Всего: 196 |
Есть еще один вариант: делаешь виртуальную функцию, которая принимает тип операции, название свойства и ссылку на значение. Ее переопределяешь в каждом потомке. В ее задачи входит определение свойства и вызов соответствующего метода. В случае с малым количеством свойств этот вариант может быть более оптимальным. |
||||
|
|||||
ZeusAtVingrad |
|
|||
Шустрый ![]() Профиль Группа: Участник Сообщений: 82 Регистрация: 12.4.2006 Репутация: нет Всего: нет |
Ну так а потомки-то все его наследуют. И В КАЖДОМ объекте класса-наследника будет такой map. А нужен только один общий для каждого класса-наследника (т.е. он должен быть static в наследнике).
Не, "узловые" функции (которые потом будут раскидывать вызовы внутри) - типа marshaling такой, - я не хочу. Я всё стремлюсь сделать на указателях, причём и их-то как можно меньше. Чтобы архитектура была максимально скорострельной. |
|||
|
||||
xvr |
|
|||
Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 7046 Регистрация: 28.8.2007 Где: Дублин, Ирландия Репутация: 60 Всего: 223 |
Вам нужны closure (с замыканием в них this и member pointer'а) и/или делегаты В С++ 11 closure наверное можно реализовать с помощью лямбда функций |
|||
|
||||
vinter |
|
||||
![]() Explorer ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2735 Регистрация: 1.4.2006 Где: Н.Новгород Репутация: 13 Всего: 56 |
Лямбда и есть closure.
Косвенный вызов функции, как правило, не является узким местом. |
||||
|
|||||
vinter |
|
|||
![]() Explorer ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2735 Регистрация: 1.4.2006 Где: Н.Новгород Репутация: 13 Всего: 56 |
Еще идея:
Сделать у базового класса следующую функцию:
И в каждом потомке переопределять соответствующим образом, кидая исключение, если функция не известна. |
|||
|
||||
ZeusAtVingrad |
|
|||
Шустрый ![]() Профиль Группа: Участник Сообщений: 82 Регистрация: 12.4.2006 Репутация: нет Всего: нет |
Ну, собственно, у меня задумка так и была реализована. Только вместо std::function<void> я бы возвращал указатель на функцию класса. Тут вопрос-то больше не в том как сообщить "кому надо" указатель на функцию, а в том - что именно сообщать. Действительно ли указатель на функцию или может быть как-то по другому решить взаимодействие объектов... Углубился в изучение C++11. Раньше его не использовал. Пока непонятно как прикрутить эти лямбды к моей ситуации. |
|||
|
||||
vinter |
|
||||
![]() Explorer ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2735 Регистрация: 1.4.2006 Где: Н.Новгород Репутация: 13 Всего: 56 |
в твоей ситуации лямды прикручиваются так же как и bind, в данном случае я бы выбрал bind, т.к. лямбды избыточны.
Про указатели на функции пора забыть, это из С с классами. Тем более, что их очень легко использовать неправильно. function не даст тебя использовать неправильно ;) Добавлено @ 19:32 например так можно сделать для класса B::foo:
или через лямбду:
Это сообщение отредактировал(а) vinter - 22.11.2013, 19:33 |
||||
|
|||||
ZeusAtVingrad |
|
|||
Шустрый ![]() Профиль Группа: Участник Сообщений: 82 Регистрация: 12.4.2006 Репутация: нет Всего: нет |
Я так понимаю, что std::bind появилась в C++11?
А ранее его надо было искать в boost? |
|||
|
||||
xvr |
|
|||
Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 7046 Регистрация: 28.8.2007 Где: Дублин, Ирландия Репутация: 60 Всего: 223 |
||||
|
||||
vinter |
|
|||
![]() Explorer ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2735 Регистрация: 1.4.2006 Где: Н.Новгород Репутация: 13 Всего: 56 |
в принципе да, в C++11(раньше он был в особом namespace std::tr1). Раньше был std::bind1st и std::bind2nd. Их, кстати, в твоём случае тоже хватит.
|
|||
|
||||
ZeusAtVingrad |
|
|||
Шустрый ![]() Профиль Группа: Участник Сообщений: 82 Регистрация: 12.4.2006 Репутация: нет Всего: нет |
Другая проблема встала Тестовый пример.
Два класса (B и C) наследника от одного (А). У наследников, у обоих есть функции получения значения int. Они могут вернуть std::function<void(int)> - чтобы можно было вызвать эти функции. Кому надо - могут сохранить, чтобы вызвать потом (т.о. установить "связь" (link) для рассылки значений). Вопрос: можно ли из базового класса (A) как-то убрать упоминание int? Потому что сигнатуры функций у классов-наследников могут быть разными и базовому классу они неизвестны. Между собой классы-наследники уж как-нибудь договорятся (вызываемый сообщит об ошибке вызывающему, если у него не оказалось функции данного типа), но вот чтобы в базовом классе A не упоминались типы функций. Мне пока видится: заворачивать возвращаемую std::function<void(int)> во что-то вроде boost::any а вызывающий пусть сравнивает - совпадает ли тип с тем, что он ожидал или нет. Ещё вариант - гонять собственно сами значения через boost::any. Тогда и шаблон в базовом классе будет выглядеть как std::function<void(const boost::any&)> - и не важно какие именно типы нужны классам-наследникам. Этот путь не очень красивый архитектурно получается (в наследниках, при программировании уже собственно компонентов) и медленнее, чем гонять значения непосредственно в их собственных типах. Или может паттерн проектирования какой-то подходящий есть и я просто туплю? И второй вопрос - касаемо этих самых std::bind'ов и std::function. В стандартной библиотеке предусмотрен механизм защиты от удаления объекта - того, на который мы имеем std::function? Чтобы программа не падала, а исключение например выбрасывала, что, мол, объекта уже нет. Или что-то вроде этого. Или придётся самому колхозить? Это сообщение отредактировал(а) ZeusAtVingrad - 25.11.2013, 23:20 |
|||
|
||||
xvr |
|
||||
Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 7046 Регистрация: 28.8.2007 Где: Дублин, Ирландия Репутация: 60 Всего: 223 |
Для того, что бы они договорились у них обоих должна присутствовать сигнатура на этапе компиляции. Тогда можно получение этой function завернуть в другой класс, у которого будет явно присутствовать тип в сигнатуре метода получения function. Все такие классы должны быть наследником одного базового (назовем его GenericSource, у него вообще не будет метода для получения function), и именно его должен возвращать A. Все конкретные сигнатуры должны быть как то перечислены (либо строкой, либо GUID'ом, либо можно попробовать явно вывести некую константу из типа), и в классе A должен быть метод для получения GenericSource по сигнатуре.
Нет Придется. Например введением общего менеджера на все объекты, который будет содержать общее оглавление всех объектов в системе (ведь как то они должны друг друга находить). Заодно он может и выступать в качестве посредника при вызове callback'ов. Так как он сам никуда не денется, то такие вызовы будут безопасны (конечно он сам должен отслеживать разпушение объектов-приемников и не пытаться передать вызов им) |
||||
|
|||||
ZeusAtVingrad |
|
|||
Шустрый ![]() Профиль Группа: Участник Сообщений: 82 Регистрация: 12.4.2006 Репутация: нет Всего: нет |
Честно говоря - я ничего в коде не понял.
Зачем шаблон, параметр которого не используется? Зачем в нём константа Trait которая равна адресу статической функции приведённой к... size_t? |
|||
|
||||
xvr |
|
|||
Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 7046 Регистрация: 28.8.2007 Где: Дублин, Ирландия Репутация: 60 Всего: 223 |
Для каждого типа шаблона будет сгенерена своя статическая функция (по 1 штуке на каждый тип). Ее адрес будет уникальный. Таким образом для каждого типа шаблона (сигнатуры вашего callback'а) получим уникальное число. |
|||
|
||||
ZeusAtVingrad |
|
|||
Шустрый ![]() Профиль Группа: Участник Сообщений: 82 Регистрация: 12.4.2006 Репутация: нет Всего: нет |
Мда. Интересный подход.
А зачем приводить к site_t? И ещё - этот способ быстрее, чем std::type_info? |
|||
|
||||
![]() ![]() ![]() |
Правила форума "С++:Общие вопросы" | |
|
Добро пожаловать!
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn |
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |