| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C/C++: Общие вопросы > Указатель на функцию-член |
| Автор: ZeusAtVingrad 21.11.2013, 22:41 | ||||
| Добрый день! Передо мной встала такая задача (или по-крайней мере у меня сложился такой путь её решения): Есть базовый класс. Известно, что у его наследников будут функции с определёнными (одинаковыми) сигнатурами, но неизвестно сколько их будет и как они будут называться. Т.е. в базовый класс их не пропишешь ни под каким видом. В то же время, код, использующий объекты производных классов, ни о чём кроме базового класса не знает. Я объявил typedef с указателем на функцию-член БАЗОВОГО класса (с нужной мне сигнатурой - в примере void). А затем, получая указатели на функции-члены ПРОИЗВОДНЫХ классов и применяя их к указателям на объекты ПРОИЗВОДНЫХ классов - вызываю функции этих производных классов. Погонял код и так, и сяк. И с виртуальными функциями и с наследованием следующего уровня - вроде бы всё работает как задумано. В рабочей версии производные классы будут лежать в shared library, грубо говоря - вызывающий код будет получать всё: и указатель на объект и указатель на функцию - имея "на руках" только строки с названиями *.so (*.dll) и функций. Вопросы: 1. Это у меня случайно работает или всё соответствует стандартам языка и будет работать и на других платформах, на других компиляторах? 2. Вообще задумка здравая или по фен-шую правильнее было бы решать эту задачу как-то по-другому?
Выводит (как я и задумывал) следующее:
|
| Автор: vinter 22.11.2013, 09:45 |
| Вообще говоря если соблюдать осторожность и использовать правильные функции на правильных объектах, то да, этот код должен работать на любой платформе. Но это подходит для домашних проектов, т.к. когда над проектом работают больше одного человека, то об осторожности можно забыть. Любое решение должно быть устойчивым, это решение абсолютно неустойчиво. Можно присвоить функцию из одного объекта - другому и тогда будет UB. Если у тебя появилось желание достучаться из предка к потомку, то значит у тебя что-то не так с архитектурой. |
| Автор: ZeusAtVingrad 22.11.2013, 09:53 |
| А если всё это завернуть в архитектуру так, чтобы программист не мог вызвать не ту функцию у не того объекта? Ограничить возможности программистам по оперированию указателями (в идеале - чтобы они этих указателей и не видели). А как по другому можно описать множество объектов про которых известно только то, что у них есть функции с определённой сигнатурой? |
| Автор: vinter 22.11.2013, 09:57 |
| ZeusAtVingrad, ты попробуй описать конкретную задачу. Быть может ты вообще не туда копаешь. |
| Автор: ZeusAtVingrad 22.11.2013, 10:45 |
| Нужен "компот" из компонентов, подгружаемых "движком" из расширяемых библиотек в соответствии с конфигом. У компонентов - набор свойств (функции set/get). Движок о них ничего не знает. Между свойствами устанавливаются связи-подписки: изменение свойства одного компонента рассылается в свойства других компонентов. В целом, задача стоИт такая. |
| Автор: vinter 22.11.2013, 11:06 |
| я всё равно ничего не понял. Нужно подробное описание, чтобы можно было что-то нормальное подсказать. Но если надо вызывать метода по какому-то событию, то можно храить контейнер из std::function<void()>, а в контейнер напихивать нужные функции через std::bind. Если нет возможности использовать современный компилятор, то можно заменить std на boost и использовать его. Так ты избавишься от проблем с неправильным присваиванием функций. Каждая функция будет связан с правильным объектом. |
| Автор: bsa 22.11.2013, 11:21 |
| ZeusAtVingrad, в этом случае обычно используется механизм подобный следующему: Базовый класс содержит контейнер свойств (std::map<const char*,std::pair<Getter,Setter> >), в который каждый потомок из конструктора добавляет свои свойства, а пользователь класса использует специальные методы базового класса для задания значений свойств. |
| Автор: ZeusAtVingrad 22.11.2013, 13:03 | ||||||||
Нужна поддержка примерно следующего (очень простой и короткий пример, для иллюстрации):
Все объекты лежат в некоем контейнере. Работают самостоятельно (у кого-то поток слушает сеть, у кого-то поток с таймером "щёлкает", кто-то получает события с графики), передают значения друг другу и всё это - работающее приложение.
Спасибо, в сторону bind'а посмотрю. Не хотелось решения типа boost::any (из-за скорости), но похоже, что всё-равно придётся использовать (и) его.
Была такая мысль. Получается, что в каждом объекте класса-наследника будет такой map? Нерационально с точки зрения использования памяти. И static его не сделаешь. Потому что для классов-наследников они всё-таки должны быть разными. И ещё: Getter и Setter - что из себя могут представлять? Опять в каком-то виде - указатель на объект и на функцию? |
| Автор: ZeusAtVingrad 22.11.2013, 13:45 | ||||||
Ну так а потомки-то все его наследуют. И В КАЖДОМ объекте класса-наследника будет такой map. А нужен только один общий для каждого класса-наследника (т.е. он должен быть static в наследнике).
Не, "узловые" функции (которые потом будут раскидывать вызовы внутри) - типа marshaling такой, - я не хочу. Я всё стремлюсь сделать на указателях, причём и их-то как можно меньше. Чтобы архитектура была максимально скорострельной. |
| Автор: xvr 22.11.2013, 13:48 | ||
Вам нужны closure (с замыканием в них this и member pointer'а) и/или делегаты В С++ 11 closure наверное можно реализовать с помощью лямбда функций |
| Автор: vinter 22.11.2013, 14:17 | ||||
Лямбда и есть closure.
Косвенный вызов функции, как правило, не является узким местом. |
| Автор: vinter 22.11.2013, 19:00 | ||
| Еще идея: Сделать у базового класса следующую функцию:
И в каждом потомке переопределять соответствующим образом, кидая исключение, если функция не известна. |
| Автор: ZeusAtVingrad 22.11.2013, 19:23 | ||
Ну, собственно, у меня задумка так и была реализована. Только вместо std::function<void> я бы возвращал указатель на функцию класса. Тут вопрос-то больше не в том как сообщить "кому надо" указатель на функцию, а в том - что именно сообщать. Действительно ли указатель на функцию или может быть как-то по другому решить взаимодействие объектов... Углубился в изучение C++11. Раньше его не использовал. Пока непонятно как прикрутить эти лямбды к моей ситуации. |
| Автор: vinter 22.11.2013, 19:29 | ||||
| в твоей ситуации лямды прикручиваются так же как и bind, в данном случае я бы выбрал bind, т.к. лямбды избыточны. Про указатели на функции пора забыть, это из С с классами. Тем более, что их очень легко использовать неправильно. function не даст тебя использовать неправильно ;) Добавлено @ 19:32 например так можно сделать для класса B::foo:
или через лямбду:
|
| Автор: ZeusAtVingrad 22.11.2013, 20:09 |
| Я так понимаю, что std::bind появилась в C++11? А ранее его надо было искать в boost? |
| Автор: xvr 22.11.2013, 22:25 |
Гораздо раньше. А вот std::function<> - в C++ 11 |
| Автор: vinter 23.11.2013, 07:19 |
| в принципе да, в C++11(раньше он был в особом namespace std::tr1). Раньше был std::bind1st и std::bind2nd. Их, кстати, в твоём случае тоже хватит. |
| Автор: ZeusAtVingrad 25.11.2013, 23:05 |
| Другая проблема встала http://ideone.com/AsTkhr. Два класса (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? Чтобы программа не падала, а исключение например выбрасывала, что, мол, объекта уже нет. Или что-то вроде этого. Или придётся самому колхозить? |
| Автор: xvr 26.11.2013, 08:52 | ||||
Для того, что бы они договорились у них обоих должна присутствовать сигнатура на этапе компиляции. Тогда можно получение этой function завернуть в другой класс, у которого будет явно присутствовать тип в сигнатуре метода получения function. Все такие классы должны быть наследником одного базового (назовем его GenericSource, у него вообще не будет метода для получения function), и именно его должен возвращать A. Все конкретные сигнатуры должны быть как то перечислены (либо строкой, либо GUID'ом, либо можно попробовать явно вывести некую константу из типа), и в классе A должен быть метод для получения GenericSource по сигнатуре.
Нет Придется. Например введением общего менеджера на все объекты, который будет содержать общее оглавление всех объектов в системе (ведь как то они должны друг друга находить). Заодно он может и выступать в качестве посредника при вызове callback'ов. Так как он сам никуда не денется, то такие вызовы будут безопасны (конечно он сам должен отслеживать разпушение объектов-приемников и не пытаться передать вызов им) |
| Автор: ZeusAtVingrad 26.11.2013, 22:05 |
| Честно говоря - я ничего в коде не понял. Зачем шаблон, параметр которого не используется? Зачем в нём константа Trait которая равна адресу статической функции приведённой к... size_t? |
| Автор: xvr 27.11.2013, 09:27 | ||
Для каждого типа шаблона будет сгенерена своя статическая функция (по 1 штуке на каждый тип). Ее адрес будет уникальный. Таким образом для каждого типа шаблона (сигнатуры вашего callback'а) получим уникальное число. |
| Автор: ZeusAtVingrad 27.11.2013, 09:47 |
| Мда. Интересный подход. А зачем приводить к site_t? И ещё - этот способ быстрее, чем std::type_info? |