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

C++

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

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

20.11.2010, 12:33. Просмотров 4613. Ответов 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++
Не получается сделать функцию, параметрами которой являются указатель на класс и на метод. Обращаться к классу нужно именно по указателю,...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1287 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
16.04.2011, 18:41 #31
Цитата Сообщение от Evg Посмотреть сообщение
А кроме этого от тебя ничего и не требуется в той задаче. Только вызвать метод надо не напрямую, а из BlackBox'а
Вообще никаких проблем тогда нет. Но функтор нужен полиморфный. Дочерние классы в виде шаблона. Это будет самым правильным и типизированным.)

Добавлено через 2 минуты
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Base
{
public:
       virtual void Exec() = 0;
};
 
template<class T>
class Functor : public Base
{
public:
      T * obj;
      void Exec()
      {
           obj->func1();
      }
};
И специализировать метод ничто не мешает.
0
Evg
Эксперт CАвтор FAQ
17810 / 6016 / 388
Регистрация: 30.03.2009
Сообщений: 16,531
Записей в блоге: 26
16.04.2011, 18:42  [ТС] #32
Цитата Сообщение от Deviaphan Посмотреть сообщение
Вообще никаких проблем тогда нет. Но функтор нужен полиморфный. Дочерние классы в виде шаблона. Это будет самым правильным и типизированным.)
Ты не умничай а ответь на поставленный выше вопрос: как реализовать внутренности BlackBox без шаблонов, дополнительных классов и прочих плясок с бубнами
0
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1287 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
16.04.2011, 19:10 #33
В дополнение к коду выше
C++
1
2
3
4
5
6
7
8
9
10
11
12
void BlackBox( Base * ptr )
{
     // какой-то код
     ptr->Exec();
}
 
// Какой-то класс с методом func1.
class MyClass;
 
MyClass a;
Functor<MyClass> f;
BlackBox(&f );
Добавлено через 46 секунд
внутренности BlackBox без шаблонов, дополнительных классов и прочих плясок с бубнами
Все пляски вокруг него. И никаких бубнов. Всё логично и просто.
0
Evg
Эксперт CАвтор FAQ
17810 / 6016 / 388
Регистрация: 30.03.2009
Сообщений: 16,531
Записей в блоге: 26
16.04.2011, 23:00  [ТС] #34
Цитата Сообщение от Deviaphan Посмотреть сообщение
внутренности BlackBox без шаблонов, дополнительных классов и прочих плясок с бубнами
Какой нафиг класс Base? Какой метод Exec? Внимательно смотри пост #26 - там написаны класс A и класс B. Более того, BlackBox - это библиотечная функция, которая вообще ни о каких пользовательских классах не знает. А классы A и B - пользовательские
0
ForEveR
В астрале
Эксперт С++
7972 / 4734 / 321
Регистрация: 24.06.2010
Сообщений: 10,541
Завершенные тесты: 3
16.04.2011, 23:50 #35
Evg, Используя расширения языка для конкретного компилятора писать код - плохая затея. Если тебе нужен только borland то и не парься, используй closure.
0
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1287 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
17.04.2011, 07:32 #36
Цитата Сообщение от Evg Посмотреть сообщение
Внимательно смотри пост #26 - там написаны класс A и класс B
Класс Base является базовым не для A и B, а для функтора. Иерархия A и B и прочих не изменяется. Раз BlackBox пишешь ты, то ничто не мешает заменить void* на Base*. Плюс ты получаешь бонус в виде возможности использовать различные типы и количество параметров для вызывемой метода и вызов более одного метода класса.
Библиотека ничего о пользовательских классах знать не будет, она будет знать только об интерфейсе функтора Base и всё.

Добавлено через 53 секунды
Я не стал писать код инициализации функтора, там просто присваивание адреса...
0
ValeryS
Модератор
6631 / 5038 / 466
Регистрация: 14.02.2011
Сообщений: 16,849
17.04.2011, 08:26 #37
Evg,
еще раз повтори(много написано запутался)
чем тебя не устраивает Callback?
для таких вещей по моему он и создан.
что значит неправильный this?
Не тот класс? Не тот экземпляр?

и чем не устраивают Мессаги?
0
Evg
Эксперт CАвтор FAQ
17810 / 6016 / 388
Регистрация: 30.03.2009
Сообщений: 16,531
Записей в блоге: 26
17.04.2011, 10:13  [ТС] #38
Цитата Сообщение от ForEveR Посмотреть сообщение
Evg, Используя расширения языка для конкретного компилятора писать код - плохая затея
Расширение используется только на входе и выходе цепочки. Эти классы так или иначе являются компонентами VCL. А в серединке делается транзитная передача данных, которая от расширений языка не зависит (т.е. ей дали какой-то набор байтов, и этот набор просто скопировался)

Цитата Сообщение от ForEveR Посмотреть сообщение
Если тебе нужен только borland то и не парься, используй closure.
Я не парюсь. Человек не понял, а я ему объясняю. Причём объясняю не решение, а поставленный вопрос.

Цитата Сообщение от Deviaphan Посмотреть сообщение
Класс Base является базовым не для A и B, а для функтора. Иерархия A и B и прочих не изменяется. Раз BlackBox пишешь ты, то ничто не мешает заменить void* на Base*. Плюс ты получаешь бонус в виде возможности использовать различные типы и количество параметров для вызывемой метода и вызов более одного метода класса.
Библиотека ничего о пользовательских классах знать не будет, она будет знать только об интерфейсе функтора Base и всё.
Ты напиши исходник хотя бы схематично, а не на словах объясняй. И, как я уже неоднократно объяснял, сделай это без шаблонов

Цитата Сообщение от ValeryS Посмотреть сообщение
Evg,
еще раз повтори(много написано запутался)
чем тебя не устраивает Callback?
для таких вещей по моему он и создан.
что значит неправильный this?
Не тот класс? Не тот экземпляр?

и чем не устраивают Мессаги?
Может я плохо выразился. Заставить свои коды работать я могу всегда. И всегда могу придумать с десяток решений: от идиотских до черезж...ых. В данном случае я пытаюсь найти решение, которое было бы максимально удобным для пользователя.

Давай вернёмся к примеру из последнего абзаца поста #24. Постановка задачи, например, такая. Пользователь пишет класс окна (TForm). Нужно скачать из инета картинку и отобразить её на форме. Если скачивание выполнять в главном процессе, то в момент скачивания приложение будет "висеть": т.е. оно никак не будет реагировать на нажатия кнопок, не будет перерисовываться если поверх него протащить другое окно и т.п. Чтобы этого избежать, надо процесс скачивания утащить в поток. Пока картинка скачивается в потоке, приложение живёт своей жизнью: реагирует на нажатия на кнопочки, менюшки и прочие события. Как только картинка скачалась, то главный процесс каким-то образом об этом узнает и вызовет код, который отрисует картинку на форму (потому что из потока рисовать нельзя). Как такой набор действий сделать максимально удобным для пользователя? У меня работает следующий вариант. Всё то, что начинается с User - это то, что пишет пользователь, а с Lib - это библиотека. Схематично:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class UserForm : public LibForm
{
  ...
 
  void UserGetImage (void)
  {
    LibStartThread (UserDownloadImage, UserDisplayImage);
  }
 
  void UserDownloadImage (void)
  {
    // Скачивание картинки (в потоке)
  }
 
  void UserDisplayImage (void)
  {
    // Отрисовка картинки (в главном процессе)
  }
}
Т.е. для того, чтобы работать с потоками, пользователю необходимо всего два дополнительных действия, которые являются максимально примитивными: добавить в родителя класс LibForm и поставить вызов библиотечного метода LibStartThread, подав параметрами указатели на собственные методы UserDownloadImage и UserDisplayImage. Эти два собственных метода так или иначе пришлось бы реализовывать, а потому они практически не являются дополнительными действиями. Каким образом библиотечный метод LibStartThread обеспечит вызов UserDownloadImage в потоке, дальнейший выход из себя (т.е. после запуска потока работа UserGetImage завершится и приложение начнёт обрабатывать сообщения) и вызов UserDisplayImage в главном процессе после завершения работы потока - это исключительно проблема библиотеки и пользователя она никоим боком не колышет. И речь об этом сейчас не идёт. Речь идёт только о том, каким образом организовать максимально удобный для пользователя интерфейс (разумеется, в пределах того, что позволяет язык)

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

Добавлено через 44 секунды
И заодно объясни, что такое Callback

Добавлено через 10 минут
Да, вот ещё. Библиотека поддерживает возможность работы без потока. Т.е. LibStartThread может запустить UserDownloadImage и UserDisplayImage последовательно в главном процессе - сие удобно для процесса отладки, если возникает подозрение, что проблема обусловлена работой с потоком. Переключение между двумя режимами опять-таки делается одним лёгким движением руки
0
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1287 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
17.04.2011, 10:40 #39
Цитата Сообщение от Evg Посмотреть сообщение
напиши исходник хотя бы схематично, а не на словах объясняй. И, как я уже неоднократно объяснял, сделай это без шаблонов
Я полностью исходник написал уже.) Шаблоны только для уменьшения ручного кодирования, BlackBox работает без шаблонов, т.е. с выносом в библиотеку трудностей не будет.
Я повторю код.
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// Интерфейс функтора. Ничего не знает о классах A,B и любых других.
class FunctorBase
{ public:
        virtual void Execute() = 0;
};
 
// Никаких шаблонов нету.
void BlackBox( FunctorBase * f )
{
        // Твой код
        // Под конец выполняем функтор
        f->Execute();
}
 
// другой файл/модуль/пофиг
// 
template<class T>
class Functor : public FunctorBase
{
public:
      Functor( T * t )
      : obj(t)
      {}
 
      void Execute()
      {
            obj->func1();
      }
 
       T * obj;
};
 
// Пользовательские классы. Ничего не знают о FunctorBase и Functor  и BlackBox
class A
{
public:
      void func1();
};
class B;
class C
{
public:
         void func1();
         void func2();
};
 
template<>
void Functor<C>::Execute()
{
     obj->func1();
     obj->func2();
}
 
void main()
{
      A a;
      B b;
      C c;
 
      Functor<A> fa(&a);
      Functor<B> fb(&b);
      Functor<C> fc(&c);
 
      BlackBox( &fa );
      BlackBox( &fb );
      BlackBox( &fc );
 
}
Добавлено через 1 минуту
Если в BlackBox передавать по ссылке, то ещё проще код сделать можно.

Добавлено через 2 минуты
Шаблоны используются только в пользовательском коде и можно обойтись без них. Это просто облегчение жизни пользователю. BlackBox шаблоны не использует.
0
Evg
Эксперт CАвтор FAQ
17810 / 6016 / 388
Регистрация: 30.03.2009
Сообщений: 16,531
Записей в блоге: 26
17.04.2011, 11:11  [ТС] #40
Deviaphan, вот сравни твой код (пост 39) и мой (пост 38). В моём случае дополнительных действий раз-два и обчёлся, а в твоём случае пользователю надо писать кучу дополнительного кода. В твоём коде нет раздельного запуска двух методов по отдельности (т.е. ты в методе Execute запускаешь их одновременно). Чтобы обеспечить раздельный запуск методов, тебе придётся описывать два пользовательских класса Functor. Не говоря уж о том, что пользователь при таком раскладе свои методы ОБЯЗАН называть func1 и func2 (в противном случае работать не будет). Т.е. твой способ подразумевает перекладывание большого количества проблем с автора библиотеки на пользователя библиотеки.

Добавлено через 9 минут
Цитата Сообщение от Evg Посмотреть сообщение
что пользователь при таком раскладе свои методы ОБЯЗАН называть func1 и func2
Либо для каждого имени метода создавать ещё один шаблон
0
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1287 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
17.04.2011, 11:49 #41
Ну да. Но зато переносимо. Можно использовать хоть стопятьсот методов, использовать разное количество и типы параметров...
В общем, везде свои недостатки.)

Добавлено через 47 секунд
Цитата Сообщение от Evg Посмотреть сообщение
Чтобы обеспечить раздельный запуск методов, тебе придётся описывать два пользовательских класса Functor.
Нет. Нужно два виртуальных метода в функторе.
0
Evg
Эксперт CАвтор FAQ
17810 / 6016 / 388
Регистрация: 30.03.2009
Сообщений: 16,531
Записей в блоге: 26
17.04.2011, 12:00  [ТС] #42
Цитата Сообщение от Deviaphan Посмотреть сообщение
Ну да. Но зато переносимо. Можно использовать хоть стопятьсот методов, использовать разное количество и типы параметров...
В общем, везде свои недостатки.)
Любая визуальная компонента любой системы по определению является непереносимой. Т.к. класс окна от борланда ты всё равно не сможешь использовать на msvs или qt. Хотя для переносимости я бы всё равно использовал транзитную обезличенную передачу: в виде void* передавал бы указатель на экземпляр и указатель на метод, а для того, чтобы обезличенные данные превратить в типизированные, завёл бы третий параметр, куда бы передал указатель на статический метод класса, внутри которого типизировал бы два обезличенных указателя. Этот вариант куда более удобен в использовании, чем технологии, требующие реализации воспомогательного класса.

Цитата Сообщение от Deviaphan Посмотреть сообщение
Нет. Нужно два виртуальных метода в функторе.
Два виртуальных метода в функторе нужно для того, чтобы передавать в BackBox два указателя, а не для того, чтобы вызывать раздельно два метода. Хотя это уже технические детали
0
taras atavin
Ушёл с форума.
3569 / 1753 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
18.04.2011, 07:58 #43
Evg, наследование предназначено для копирожания дефолта, перекрытие членов предка - для того, чтоб в потомке сделать свой отличающийся дефолт, а если тебе каждый раз нужны другие дейтсвия, то или на каждый случай делай своего потмока, или выбрось к чёрту это долбаное наследование и делай через указатели. Зачем смешивать?
0
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1287 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
18.04.2011, 08:06 #44
Цитата Сообщение от taras atavin Посмотреть сообщение
наследование предназначено для копирожания дефолта
Ничего подобного.
Где тут дефолт?
C++
1
2
3
4
5
6
class IX
{ 
public: 
    virtual void Fx1() = 0; 
    virtual void Fx2() = 0; 
};
0
taras atavin
Ушёл с форума.
3569 / 1753 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
18.04.2011, 08:15 #45
А разве эти члены наследуются? Здесь то как раз не надо Страуструпом быть, чтоб увидеть гарантирвоанное перекрытие. Причём, в каждом конкретном потомке оно будет всегда одно и тоже.
0
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
18.04.2011, 08:15
Привет! Вот еще темы с ответами:

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

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

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

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


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

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

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