Модераторы: Daevaorn

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Указатель на функцию-член, Использование сабжа для классов-потомков 
:(
    Опции темы
ZeusAtVingrad
Дата 21.11.2013, 22:41 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 82
Регистрация: 12.4.2006

Репутация: нет
Всего: нет



Добрый день!

Передо мной встала такая задача (или по-крайней мере у меня сложился такой путь её решения):

Есть базовый класс.
Известно, что у его наследников будут функции с определёнными (одинаковыми) сигнатурами, но неизвестно сколько их будет и как они будут называться. Т.е. в базовый класс их не пропишешь ни под каким видом.
В то же время, код, использующий объекты производных классов, ни о чём кроме базового класса не знает.

Я объявил typedef с указателем на функцию-член БАЗОВОГО класса (с нужной мне сигнатурой - в примере void).
А затем, получая указатели на функции-члены ПРОИЗВОДНЫХ классов и применяя их к указателям на объекты ПРОИЗВОДНЫХ классов - вызываю функции этих производных классов.
Погонял код и так, и сяк. И с виртуальными функциями и с наследованием следующего уровня - вроде бы всё работает как задумано.
В рабочей версии производные классы будут лежать в shared library, грубо говоря - вызывающий код будет получать всё: и указатель на объект и указатель на функцию - имея "на руках" только строки с названиями *.so (*.dll) и функций.

Вопросы:
1. Это у меня случайно работает или всё соответствует стандартам языка и будет работать и на других платформах, на других компиляторах?

2. Вообще задумка здравая или по фен-шую правильнее было бы решать эту задачу как-то по-другому?

Код

#include <iostream>

class A
{
    public:
    virtual    ~A    () {};
    virtual    void    func    () { std::cout << "A::" << __func__ << std::endl; };
};

typedef    void    (A::* Afunction) ();

class B: public A
{
    public:
    B (): i (10) {};
//    void    func    () { std::cout << "B::" << __func__ << std::endl; };
    virtual    void    func1    () { std::cout << "B::" << __func__ << ", i=" << i++ << std::endl; };
    virtual    void    func2    () { std::cout << "B::" << __func__ << ", i=" << i++ << std::endl; };

    private:
    int i;
};

class BB: public B
{
    public:
    BB (): j (100) {};
    virtual    void    func    () { std::cout << "BB::" << __func__ << ", j=" << j++ << std::endl; };
    virtual    void    func1    () { std::cout << "BB::" << __func__ << ", j=" << j++ << std::endl; };
    virtual    void    func2    () { std::cout << "BB::" << __func__ << ", j=" << j++ << std::endl; };

    private:
    int j;
};

class C: public A
{
    public:
    C (): f (1.1) {};
    virtual    void    func    () { std::cout << "C::" << __func__ << ", f=" << f++ << std::endl; };
    virtual    void    func1    () { std::cout << "C::" << __func__ << ", f=" << f++ << std::endl; };
    virtual    void    func2    () { std::cout << "C::" << __func__ << ", f=" << f++ << std::endl; };

    private:
    float f;
};

class CC: public A
{
    public:
    CC (): d (-1.1) {};
    virtual    void    func    () { std::cout << "CC::" << __func__ << ", d=" << d-- << std::endl; };
    virtual    void    func1    () { std::cout << "CC::" << __func__ << ", d=" << d-- << std::endl; };
    virtual    void    func2    () { std::cout << "CC::" << __func__ << ", d=" << d-- << std::endl; };

    private:
    float d;
};

int
main ()
{
    A*        a    = new B();
    Afunction    af    = static_cast<Afunction>(&B::func);
    (a->*af)();
    af = static_cast<Afunction>(&B::func1);
    (a->*af)();
    af = static_cast<Afunction>(&B::func2);
    (a->*af)();
    delete a;

    a = new BB();
    af    = static_cast<Afunction>(&BB::func);
    (a->*af)();
    af = static_cast<Afunction>(&BB::func1);
    (a->*af)();
    af = static_cast<Afunction>(&BB::func2);
    (a->*af)();
    delete a;

    a = new C();
    af    = static_cast<Afunction>(&C::func);
    (a->*af)();
    af = static_cast<Afunction>(&C::func1);
    (a->*af)();
    af = static_cast<Afunction>(&C::func2);
    (a->*af)();
    delete a;

    a = new CC();
    af    = static_cast<Afunction>(&CC::func);
    (a->*af)();
    af = static_cast<Afunction>(&CC::func1);
    (a->*af)();
    af = static_cast<Afunction>(&CC::func2);
    (a->*af)();
    delete a;

    return 0;
};


Выводит (как я и задумывал) следующее:
Код

A::func
B::func1, i=10
B::func2, i=11
BB::func, j=100
BB::func1, j=101
BB::func2, j=102
C::func, f=1.1
C::func1, f=2.1
C::func2, f=3.1
CC::func, d=-1.1
CC::func1, d=-2.1
CC::func2, d=-3.1


Это сообщение отредактировал(а) ZeusAtVingrad - 21.11.2013, 22:43
PM MAIL   Вверх
vinter
Дата 22.11.2013, 09:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Explorer
****


Профиль
Группа: Завсегдатай
Сообщений: 2735
Регистрация: 1.4.2006
Где: Н.Новгород

Репутация: 13
Всего: 56



Вообще говоря если соблюдать осторожность и использовать правильные функции на правильных объектах, то да, этот код должен работать на любой платформе. Но это подходит для домашних проектов, т.к. когда над проектом работают больше одного человека, то об осторожности можно забыть. Любое решение должно быть устойчивым, это решение абсолютно неустойчиво. Можно присвоить функцию из одного объекта - другому и тогда будет UB. 

Если у тебя появилось желание достучаться из предка к потомку, то значит у тебя что-то не так с архитектурой. 



--------------------
Мой блог
PM MAIL WWW   Вверх
ZeusAtVingrad
Дата 22.11.2013, 09:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 82
Регистрация: 12.4.2006

Репутация: нет
Всего: нет



А если всё это завернуть в архитектуру так, чтобы программист не мог вызвать не ту функцию у не того объекта? Ограничить возможности программистам по оперированию указателями (в идеале - чтобы они этих указателей и не видели).


А как по другому можно описать множество объектов про которых известно только то, что у них есть функции с определённой сигнатурой?
PM MAIL   Вверх
vinter
Дата 22.11.2013, 09:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Explorer
****


Профиль
Группа: Завсегдатай
Сообщений: 2735
Регистрация: 1.4.2006
Где: Н.Новгород

Репутация: 13
Всего: 56



ZeusAtVingrad, ты попробуй описать конкретную задачу. Быть может ты вообще не туда копаешь.


--------------------
Мой блог
PM MAIL WWW   Вверх
ZeusAtVingrad
Дата 22.11.2013, 10:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 82
Регистрация: 12.4.2006

Репутация: нет
Всего: нет



Нужен "компот" из компонентов, подгружаемых "движком" из расширяемых библиотек в соответствии с конфигом. У компонентов - набор свойств (функции set/get).
Движок о них ничего не знает.
Между свойствами устанавливаются связи-подписки: изменение свойства одного компонента рассылается в свойства других компонентов.

В целом, задача стоИт такая.
PM MAIL   Вверх
vinter
Дата 22.11.2013, 11:06 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Explorer
****


Профиль
Группа: Завсегдатай
Сообщений: 2735
Регистрация: 1.4.2006
Где: Н.Новгород

Репутация: 13
Всего: 56



я всё равно ничего не понял. Нужно подробное описание, чтобы можно было что-то нормальное подсказать.
Но если надо вызывать метода по какому-то событию, то можно храить контейнер из std::function<void()>, а в контейнер напихивать нужные функции через std::bind. Если нет возможности использовать современный компилятор, то можно заменить std на boost и использовать его. Так ты избавишься от проблем с неправильным присваиванием функций. Каждая функция будет связан с правильным объектом.


--------------------
Мой блог
PM MAIL WWW   Вверх
bsa
Дата 22.11.2013, 11:21 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Модератор
Сообщений: 9185
Регистрация: 6.4.2006
Где: Москва, Россия

Репутация: 63
Всего: 196



ZeusAtVingrad, в этом случае обычно используется механизм подобный следующему: Базовый класс содержит контейнер свойств (std::map<const char*,std::pair<Getter,Setter> >), в который каждый потомок из конструктора добавляет свои свойства, а пользователь класса использует специальные методы базового класса для задания значений свойств.
PM   Вверх
ZeusAtVingrad
Дата 22.11.2013, 13:03 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 82
Регистрация: 12.4.2006

Репутация: нет
Всего: нет



Цитата

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

Нужна поддержка примерно следующего (очень простой и короткий пример, для иллюстрации):
Код

<project>
<obj lib="timer.so" id="timer"/>
<obj lib="jktrigger.so" id="trigger">
<set property="j">1</set>
<set property="k">1</set>
<link>trigger.sync=timer.tick</link>
</obj>
</project>

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

Цитата

Но если надо вызывать метода по какому-то событию, то можно храить контейнер из std::function<void()>, а в контейнер напихивать нужные функции через std::bind.

Спасибо, в сторону bind'а посмотрю.
Не хотелось решения типа boost::any (из-за скорости), но похоже, что всё-равно придётся использовать (и) его.


Цитата

в этом случае обычно используется механизм подобный следующему: Базовый класс содержит контейнер свойств (std::map<const char*,std::pair<Getter,Setter> >), в который каждый потомок из конструктора добавляет свои свойства, а пользователь класса использует специальные методы базового класса для задания значений свойств.

Была такая мысль.
Получается, что в каждом объекте класса-наследника будет такой map? Нерационально с точки зрения использования памяти.
И static его не сделаешь. Потому что для классов-наследников они всё-таки должны быть разными.
И ещё: Getter и Setter - что из себя могут представлять? Опять в каком-то виде - указатель на объект и на функцию?
PM MAIL   Вверх
bsa
Дата 22.11.2013, 13:25 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Модератор
Сообщений: 9185
Регистрация: 6.4.2006
Где: Москва, Россия

Репутация: 63
Всего: 196



Цитата(ZeusAtVingrad @  22.11.2013,  14:03 Найти цитируемый пост)
Получается, что в каждом объекте класса-наследника будет такой map?
он должен находиться в родительском классе, потомки лишь туда добавляют записи.

Цитата(ZeusAtVingrad @  22.11.2013,  14:03 Найти цитируемый пост)
И ещё: Getter и Setter - что из себя могут представлять? Опять в каком-то виде - указатель на объект и на функцию?
а иначе никак.


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

PM   Вверх
ZeusAtVingrad
Дата 22.11.2013, 13:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 82
Регистрация: 12.4.2006

Репутация: нет
Всего: нет



Цитата(bsa @ 22.11.2013,  13:25)
Цитата(ZeusAtVingrad @  22.11.2013,  14:03 Найти цитируемый пост)
Получается, что в каждом объекте класса-наследника будет такой map?
он должен находиться в родительском классе, потомки лишь туда добавляют записи.

Ну так а потомки-то все его наследуют.
И В КАЖДОМ объекте класса-наследника будет такой map.
А нужен только один общий для каждого класса-наследника (т.е. он должен быть static в наследнике).

Цитата(ZeusAtVingrad @  22.11.2013,  14:03 Найти цитируемый пост)

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

Не, "узловые" функции (которые потом будут раскидывать вызовы внутри) - типа marshaling такой, - я не хочу.
Я всё стремлюсь сделать на указателях, причём и их-то как можно меньше. Чтобы архитектура была максимально скорострельной.
PM MAIL   Вверх
xvr
Дата 22.11.2013, 13:48 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 7046
Регистрация: 28.8.2007
Где: Дублин, Ирландия

Репутация: 60
Всего: 223



Цитата(ZeusAtVingrad @  22.11.2013,  10:45 Найти цитируемый пост)
Между свойствами устанавливаются связи-подписки: изменение свойства одного компонента рассылается в свойства других компонентов.

Вам нужны closure (с замыканием в них this и member pointer'а) и/или делегаты

В С++ 11 closure наверное можно реализовать с помощью лямбда функций


PM MAIL   Вверх
vinter
Дата 22.11.2013, 14:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Explorer
****


Профиль
Группа: Завсегдатай
Сообщений: 2735
Регистрация: 1.4.2006
Где: Н.Новгород

Репутация: 13
Всего: 56



Цитата

В С++ 11 closure наверное можно реализовать с помощью лямбда функций

Лямбда и есть closure.
Цитата

Я всё стремлюсь сделать на указателях, причём и их-то как можно меньше. Чтобы архитектура была максимально скорострельной.

Косвенный вызов функции, как правило, не является узким местом.


--------------------
Мой блог
PM MAIL WWW   Вверх
vinter
Дата 22.11.2013, 19:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Explorer
****


Профиль
Группа: Завсегдатай
Сообщений: 2735
Регистрация: 1.4.2006
Где: Н.Новгород

Репутация: 13
Всего: 56



Еще идея:
Сделать у базового класса следующую функцию:
Код

virtual std::function<void()> getFunction(const char* name) = 0;

И в каждом потомке переопределять соответствующим образом, кидая исключение, если функция не известна.


--------------------
Мой блог
PM MAIL WWW   Вверх
ZeusAtVingrad
Дата 22.11.2013, 19:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 82
Регистрация: 12.4.2006

Репутация: нет
Всего: нет



Цитата(vinter @  22.11.2013,  19:00 Найти цитируемый пост)
Еще идея:
Сделать у базового класса следующую функцию:
код C++
1:
virtual std::function<void()> getFunction(const char* name) = 0;

И в каждом потомке переопределять соответствующим образом, кидая исключение, если функция не известна. 

Ну, собственно, у меня задумка так и была реализована.
Только вместо std::function<void> я бы возвращал указатель на функцию класса.
Тут вопрос-то больше не в том как сообщить "кому надо" указатель на функцию, а в том - что именно сообщать.
Действительно ли указатель на функцию или может быть как-то по другому решить взаимодействие объектов...

Углубился в изучение C++11. Раньше его не использовал.
Пока непонятно как прикрутить эти лямбды к моей ситуации.
PM MAIL   Вверх
vinter
Дата 22.11.2013, 19:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Explorer
****


Профиль
Группа: Завсегдатай
Сообщений: 2735
Регистрация: 1.4.2006
Где: Н.Новгород

Репутация: 13
Всего: 56



в твоей ситуации лямды прикручиваются так же как и bind,  в данном случае я бы выбрал bind, т.к. лямбды избыточны.
Про указатели на функции пора забыть, это из С с классами. Тем более, что их очень легко использовать неправильно. function не даст тебя использовать неправильно ;)

Добавлено @ 19:32
например так можно сделать для класса B::foo:
Код

std::function<void()> getFunction(const char* name)
{
     return std::bind(&B::foo, this);
}

или через лямбду:
Код

std::function<void()> getFunction(const char* name)
{
     return [this]{foo();};
}


Это сообщение отредактировал(а) vinter - 22.11.2013, 19:33


--------------------
Мой блог
PM MAIL WWW   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1146 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.