Форум программистов, компьютерный форум CyberForum.ru
Наши страницы

C++

Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 36, средняя оценка - 4.67
Evg
Эксперт CАвтор FAQ
17802 / 6008 / 387
Регистрация: 30.03.2009
Сообщений: 16,511
Записей в блоге: 26
#1

Использование указателя на метод вместо виртуального метода - C++

20.11.2010, 12:33. Просмотров 4606. Ответов 45
Метки нет (Все метки)

Имеется базовый класс Base. Имеется производный от Base класс Derived. В классе Derived требуется выполнить некоторое действие, которое практически полностью эквивалентно для любого производного от Base класса, за исключением небольшого фрагмента.

Схематично код выглядит так:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Base
{
  void Exec (void)
  {
    // общие действия
    ...
 
    // конкретные действия для производных классов
    Tail ();
  }
 
  virtual void Tail (void) = 0;
};
 
class Derived : public Base
{
  void MyExec (void)
  {
    // В процессе исполнения будет исполнен виртуальный метод Tail
    Exec ();
  }
 
  virtual void Tail (void)
  {
    // Определяем конкретные действия для нашего класса
  }
}
Однако при такой схеме возможен только один вид частных действия для производного класса. А хотелось бы, чтобы внутри производного класса можно было выполнять разные действия. Для этого логично было бы использовать указатель на метод. Т.е. что-то типа такого

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Base
{
  void Exec (void (Base::*tail)(void))
  {
    // общие действия
    ...
 
    // конкретные действия для производных классов
    // здесь уже через указатель  на метод
    (this->*tail) ();
  }
};
 
class Derived : public Base
{
  void MyExec (void)
  {
    // Выполняем основные действия с двумя разными типами
    // частных действий. Необходимую операцию преобразования
    // указателя на метод опускаю, чтобы глаза не резало
    Exec (Tail1);
    Exec (Tail2);
  }
 
  void Tail1 (void)
  {
    // Определяем конкретные действия "вариант1" для нашего класса
  }
 
  void Tail2 (void)
  {
    // Определяем конкретные действия "вариант2" для нашего класса
  }
}
Собственно вопрос: насколько опасным является преобразование указателя на метод в данном случае. Опасность не столько в преобразовании указателя на метод, сколько в том, что при вызове Derived-метода через указатель на класс Base может прийти кривой this. Понятно, что в случае "простого" наследования ничего страшного нет. Будет ли что-то криво работать в случае множественных или виртуальных наследований или в каких-то ещё тяжёлых случаев. Или такой способ при некоторых ограничениях на тип наследования можно считать надёжным и корректным? Можно ли как-то сделать статический контроль в случаях, когда такой вариант оказывается некорректным?
0
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
20.11.2010, 12:33
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Использование указателя на метод вместо виртуального метода (C++):

Практическое использование метода экстраполяции - C++ Builder
Ув.программисты,подскажите пожалуйста какой то пример использования метода экстраполяций для поиска в одномерном массиве.Бо перешарил инет...

Почему используется *& вместо простого указателя и как это работает? - Visual C++
может кто-нибудь объяснить почему используется *& вместо простого указателя и как это работает? конкретно у метода D class proizvodcl :...

LPTSTR (непонятное использование указателя) - C++ WinAPI
Насколько я понял LPTSTR это wchar_t* или char* в зависимости от того установлен ли UNICODE.. если это так, то я немного не понимаю...

Вызов виртуального метода базового класса из указателя производного - C++
Допустим есть такой код: #include <iostream> class Base { public: virtual void f() { std::cout << "Base\n"; } ...

Почему при переопределении виртуального метода в производном классе выводится метод базового? - C++
Всем добра! Помогите разобраться почему при переопределении виртуального метода в производном классе выводится метод базового ? По идеи...

Как сделать функцию от указателя на класс и указателя на метод? - C++
Не получается сделать функцию, параметрами которой являются указатель на класс и на метод. Обращаться к классу нужно именно по указателю,...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Manjak
269 / 175 / 7
Регистрация: 12.03.2010
Сообщений: 494
21.11.2010, 17:00 #16
В каждом конкретном случае описывать такой себе ThreadDummy действительно просто, но сделать универсальный обьект для разнотипных функций с переменным числом параметров - тяжко на старых возможностях. Тот шаблон что я выставил можно немного переделать и сделать просто внутреннюю функцию потока, внутри которой будет вызываться переданная ей функция и колбек в конце.

C++
1
2
3
4
5
6
7
8
9
10
11
static unsigned __stdcall ThreadDummy ( void * arg_) // аргументом будет кортеж данных- аргументов функции
{
    __try
    {
        StartAddr(...);
    }
    __finally
    {
        ComplitionCallback();
    }
}
Но чтобы полностью имплементировать такой обьект без сторонних наработок нужно много поработать
0
Evg
Эксперт CАвтор FAQ
17802 / 6008 / 387
Регистрация: 30.03.2009
Сообщений: 16,511
Записей в блоге: 26
21.11.2010, 17:36  [ТС] #17
Цитата Сообщение от Manjak Посмотреть сообщение
но сделать универсальный обьект для разнотипных функций с переменным числом параметров
Такая задача не стоит. У меня Execute1, Execute2, Execute3 реализованы внутри одного класса, так что в Execute1 я записываю поля класса, а в Execute2 - читаю. Не самое красивое решение, но достаточно безгеморройное
0
Evg
Эксперт CАвтор FAQ
17802 / 6008 / 387
Регистрация: 30.03.2009
Сообщений: 16,511
Записей в блоге: 26
15.04.2011, 19:49  [ТС] #18
Итого по ходу дела я нашёл решение для своего вопроса. Мне это нужно было для Borlnad'а, а у Borland'а есть ацкое расширнеие __closure: http://www.goforvbsix.ru/news/closur...2011-01-21-385 По ссылке текст корявый (переведён автоматическим переводчиком), но суть следующая. Если объявить указатель на функцию с модификатором __closure, то такой "указатель" будет содержать в себе на самом деле два указателя: указатель на метод и указатель на экземпляр класса в момент присваивания. Что важно, указатель на метод и указатель на экземпляр класса обезличенные: т.е. никакой информации о конкретном типе нет, а потому такую конструкцию можно протащить через транзитный код. В точке вызова по такому указателю компилятор выдерет из него адрес функции и ажрес экземпляра (по сути дела this) и по своим внутренним правилам построит вызов метода. Именно в этом месте и хранится невозможность использования данной фичи в виде обезличенных двух указателей на void, потому что после этого на языке нет возможности описать вызов метода без подпольных знаний о том, как это делает компилятор. Насколько я понимаю, подобной конструкции в языке Си++ нет, а жаль.

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

Добавлено через 2 часа 3 минуты
С этой конструкцией код будет выглядеть так:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Base
{
  void Exec (void (__closure *tail)(void))
  {
    // Общие действия
    ...
 
    // Конкретные действия для производных классов
    // здесь уже через tail, который содержит в себе указатель
    // на метод и указатель на экземпляр
    tail();
  }
};
 
class Derived : public Base
{
  void MyExec (void)
  {
    // Выполняем основные действия с двумя разными типами
    // частных действий. Вызов сути эквивалентен
    // "Exec (&this->Tail1)" и в closure-указатель копируется
    // this и адрес метода Tail1
    Exec (Tail1);
    Exec (Tail2);
  }
 
  void Tail1 (void)
  {
    // Определяем конкретные действия "вариант1" для нашего класса
  }
 
  void Tail2 (void)
  {
    // Определяем конкретные действия "вариант2" для нашего класса
  }
};
0
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1287 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
16.04.2011, 12:45 #19
А если вместо указателя на метод передавать функтор? В нём можно и this "точно правильный" передать.)
0
Evg
Эксперт CАвтор FAQ
17802 / 6008 / 387
Регистрация: 30.03.2009
Сообщений: 16,511
Записей в блоге: 26
16.04.2011, 14:01  [ТС] #20
Цитата Сообщение от Deviaphan Посмотреть сообщение
А если вместо указателя на метод передавать функтор? В нём можно и this "точно правильный" передать.)
Если я правильно понимаю смысл концепции функтора, то его можно использовать только тогда, когда в точке вызова известен тип. Такое можно делать в шаблонах. Но оно не годится для транзитной передачи в код, не являющийся шаблоном

Если я что-то не так понимаю, то перепиши код из поста #18 с использованием функтора. Для меня важно, чтобы внутренности класса Base НЕ являлись открытыми (т.е. не были шаблоном и не были описаны внутри описания класса)
0
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1287 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
16.04.2011, 14:55 #21
А так нельзя?
C++
1
2
3
4
5
6
7
8
9
10
11
 void MyExec (void)
  {
    // Выполняем основные действия с двумя разными типами
    // частных действий. Вызов сути эквивалентен
    // "Exec (&this->Tail1)" и в closure-указатель копируется
    // this и адрес метода Tail1
    Exec ();
    Tail1();
    Exec ();
    Tail2();
  }
Или так?
C++
1
2
3
4
5
 void Tail1 (void)
  {
     Exec();
    // Определяем конкретные действия "вариант1" для нашего класса
  }
0
Evg
Эксперт CАвтор FAQ
17802 / 6008 / 387
Регистрация: 30.03.2009
Сообщений: 16,511
Записей в блоге: 26
16.04.2011, 15:30  [ТС] #22
Цитата Сообщение от Deviaphan Посмотреть сообщение
А так нельзя?
Конечно можно. Можно и функцией qsort не пользоваться, чтобы не работать с указателем на функцию, а ручками написать сортировку. Классом std::string тоже можно не пользоваться, а пользоваться malloc'ами и всё делать ручками. И так и так будет работать, только в одном случае интерфейсами пользоваться удобно и программу развивать несложно, а в другом - нет
0
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1287 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
16.04.2011, 16:03 #23
Цитата Сообщение от Deviaphan Посмотреть сообщение
Или так?
Сложность вызова даже уменьшится, т.к. вместо передачи параметра, будет "параметр" "вызываться".

Просто, не совсем ясна суть манипуляций, но тебе виднее, конечно, продолжай извращаться.)
0
Evg
Эксперт CАвтор FAQ
17802 / 6008 / 387
Регистрация: 30.03.2009
Сообщений: 16,511
Записей в блоге: 26
16.04.2011, 17:14  [ТС] #24
Цитата Сообщение от Deviaphan Посмотреть сообщение
Просто, не совсем ясна суть манипуляций, но тебе виднее, конечно, продолжай извращаться.)
Мне кажется, что называть что-то извращением только потому, что ты чего-то не понял - это не правильно.

Общая постановка задачи. Есть чёрный ящик, в этот чёрный ящик надо отдать пару "указатель на экземпляр + указатель на метод". Вариант с классами Basi и Dervied я привёл условно, чтобы можно было описать пример того, что именно хочется сделать через указатель на метод. Для моего примера конечно же можно сделать вызов Exec, а потом вызов Tail1. Только это для упрощённой модели можно так просто сделать. Расширенный вариант выглядит так: в метод Exec тащится __closure-указатель (который есть пара "указатель на экземпляр + указатель на метод"). Внутри метода Exec создаётся поток, в потоке запускается программа, написанная на Lua, в неё передаётся моя пара, транзитно пролетает через коды на Lua и "внизу" вызывает код на Си++, который делает вызов через эту пару.

Зачем так сложно? Любую сложный интерфейс, который взаимодействует с пользователем, можно написать двумя способами. Первый способ заключается в том, чтобы реализацию интерфейса сделать простой, сам интерфейс не очень удобный для пользователя и переложить все свои проблемы на плечи пользователя. Второй способ заключается в том, что реализацию интерфейса сделать сложной, но при этом получив простой интерфейс, который будет удобным в использовании. Я всегда иду по второму пути (если это возможно)

Пример неудобного в использовании интерфейса: борландовский класс TThread. Чтобы им пользоваться. надо определить свой класс, который является производным классом от TThread, переопределить метод Execute, реализовать событие OnTerminate, в своём основном классе обеспечить возможность для доступа ко внутренним данным и методам (потому что часть, исполняемая в потоке, реализована в другом классе). Для маленькой программы конечно же это всё сойдёт, но когда пишешь что-то большое - то разводится очень много мусора вокруг создания потока. Оговорюсь сразу, что это не претензия к борланду: этот класс является лишь некоторым классом низкого уровня, над которым пользователь будет строить обёртку, удобную в конкретной программе. Просто пытался показать пример неудобного интерфейса и как его сделать удобным

Удобным в использовании была бы надстройка над таким интерфейсом. К нашему классу (с которым мы работаем и который будет рожать поток) нам нужно добавить в родители некий базовый класс, а в момент рождения потока дёрнуть метод с прототипом типа "StartThread (f1, f2)", где f1 - это указатель на метод, который будет запущен при старте потока, f2 - указатель на метод, который будет запущен в главном процессе после завершения потока. Такой интерфейс очевидным образом использовать намного удобнее
0
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1287 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
16.04.2011, 17:23 #25
Про "извращённость" я сказал из-за того, что ты там выше писал, что тип не известен. В (почти строго) типизированном языке избавляться от типов лично мне кажется извращением.)
Этот код делает то же самое, но без каких либо вопросов:
C++
1
2
3
4
5
 void Tail1 (void)
  {
     Exec();
    // Определяем конкретные действия "вариант1" для нашего класса
  }
Собственно и для использования это проще
C++
1
2
3
4
5
A a;
a.Exec( A::Tail );
 
B b;
a.Tail();
0
Evg
Эксперт CАвтор FAQ
17802 / 6008 / 387
Регистрация: 30.03.2009
Сообщений: 16,511
Записей в блоге: 26
16.04.2011, 17:41  [ТС] #26
Цитата Сообщение от Deviaphan Посмотреть сообщение
В (почти строго) типизированном языке избавляться от типов лично мне кажется извращением.)
В "(почти строго) типизированном языке" НЕ избавляться от типов можно только путём раздувания кода (что мы и имеем на примере шаблонов Си++). И больше никак. Дополнительным побочным эффектом является то, что нет никакой возможности спрятать код.

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

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void BlackBox (void *this, void (*method)(void))
{
  ...
}
 
class A
{
  ...
  void func1 (void);
  void func2 (void);
};
 
class B
{
  ...
  void func1 (void);
  void func2 (void);
};
 
...
A a;
B b;
 
// Нужно, чтобы в конечном итоге как бы вызвалось a.func1()
BlackBox (&a, &A::func1);
 
// Нужно, чтобы в конечном итоге как бы вызвалось a.func2()
BlackBox (&a, &A::func2);
 
// Нужно, чтобы в конечном итоге как бы вызвалось b.func1()
BlackBox (&b, &B::func1);
 
// Нужно, чтобы в конечном итоге как бы вызвалось b.func2()
BlackBox (&b, &B::func2);
...
Напиши внутренности "чёрного ящика", чтобы вызовы BlackBox соответствовали тому, что написано в комментариях перед вызовами. Особо подчёркаваю: BlackBox НЕ является шаблоном
0
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1287 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
16.04.2011, 18:08 #27
C++
1
2
3
4
5
 void func1 (void)
{
        BlackBox( this );
        ...
}
Причём, BlackBox можно сделать полем объекта. Т.е. будет простой указатель на функцию.
0
Evg
Эксперт CАвтор FAQ
17802 / 6008 / 387
Регистрация: 30.03.2009
Сообщений: 16,511
Записей в блоге: 26
16.04.2011, 18:10  [ТС] #28
В задаче вообще-то требуется написать внутренности функции BlackBox, а не метода func1
0
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1287 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
16.04.2011, 18:20 #29
Тебе же нужно после выполнения blackbox выполнить func?
При этом тип объекта неизвестен (через void*)?
Как ты будешь выполнять какую-либо работу в blackbox, если тебе не известен тип объекта?
Всё что ты сможешь сделать, это вызвать переданный метод func.
Вот потому и непонятно мне ничего.(

Цитата Сообщение от Evg Посмотреть сообщение
вообще-то требуется написать внутренности функции BlackBox
Чёрный ящик не пишут, его используют.)
0
Evg
Эксперт CАвтор FAQ
17802 / 6008 / 387
Регистрация: 30.03.2009
Сообщений: 16,511
Записей в блоге: 26
16.04.2011, 18:27  [ТС] #30
Цитата Сообщение от Deviaphan Посмотреть сообщение
Тебе же нужно после выполнения blackbox выполнить func?
Не после, а во время

Цитата Сообщение от Deviaphan Посмотреть сообщение
Как ты будешь выполнять какую-либо работу в blackbox, если тебе не известен тип объекта?
Я же несколько раз писал, что внутри чёрного ящика я не буду выполнять работу над объектом. Я этот указатель транзитно передаю и в какой-то момент внутри этого чёрного ящика вызовется переданный метод для переданного экземпляра. Но напрямую работать с экземпляром внутри чёрного ящика я не буду

Цитата Сообщение от Deviaphan Посмотреть сообщение
Всё что ты сможешь сделать, это вызвать переданный метод func
А кроме этого от тебя ничего и не требуется в той задаче. Только вызвать метод надо не напрямую, а из BlackBox'а
0
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
16.04.2011, 18:27
Привет! Вот еще темы с ответами:

Вызов виртуального метода в конструкторе - C++
Помню расматривался этот вопрос на форуме - хочу освежить память почему при вызове виртуального метода внутри конструктора UB

Вызов виртуального метода при создании - C++
Добрый вечер, библиотека навязала следующее поведение: - объект создан и валиден, если выделена память, вызван конструктор и...

Вызов виртуального метода класса наследника из вектора - C++
#include <iostream> #include <vector> using namespace std; class A { public: virtual void print() { cout << "A" <<...

Реализация виртуального метода в двух классах-наследниках - C++
Здравствуйте! Есть три класса, абстрактный CGraphicsObject, и два его наследника Circle и Parallelogram В CGraphicsObject содержится...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
Yandex
Объявления
16.04.2011, 18:27
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru