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

Неочевидные грабли полиморфизма с++ - C++

Восстановить пароль Регистрация
 
 
Рейтинг: Рейтинг темы: голосов - 52, средняя оценка - 4.79
Bers
Заблокирован
23.11.2011, 01:05     Неочевидные грабли полиморфизма с++ #1
Наткнулся в интернете на любопытный код.
Спешу поделиться с сообществом.
Просто, что бы кто если не в курсе - узнал, и не попал на эти грабли:


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
#include <iostream>
using namespace std;
 
struct A
{
    virtual void Func1(){cout<<"A::Func1"<<endl;}
};
struct B
{ 
    virtual void Func2(){cout<<"B::Func2"<<endl;}}
;
struct C: public A, public B
{ 
    virtual void Func3(){cout<<"C::Func3"<<endl;}
};
 
int main()
{
    A *ptr = new C;
    void *ptr1 = new C;
    
    ((A*)ptr)->Func1(); //вывод: A::Func1
    ((B*)ptr)->Func2(); //вывод: A::Func1 Почему?
    ((C*)ptr)->Func3(); //вывод: C::Func3
    
    A* aPtr=dynamic_cast<A*>(ptr);
    B* bPtr=dynamic_cast<B*>(ptr);
    C* cPtr=dynamic_cast<C*>(ptr)
    
    //нельзя кастовать от void*
    //void* ничего не знает ни о каких классах
    //и ни о каких таблицах виртуальных функций
 
    C* cPtr=dynamic_cast<C*>(ptr1); //ошибка компиляции
    //error C2681: void *: недопустимый тип выражения для dynamic_cast
    
    aPtr->Func1(); //вывод: A::Func1
    bPtr->Func2(); //вывод: B::Func2
    cPtr->Func3(); //вывод: C::Func3
 
    delete ptr;
    delete ptr1;
 
    return 0;
}
От себя могу добавить, что разные компиляторы по разному могут реализовывать механизм полиморфизма. Поэтому, при множественном наследовании если что и спасет - только dynamic_cast
Но в любом случае, нужно быть предельно осторожным.

А по сути, полиморфизм + множественное наследование = мина замедленного действия.

Поэтому, при проектировании полиморфных классов, лучше до последнего избегать множественного наследования.
Лучшие ответы (1)
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
23.11.2011, 01:05     Неочевидные грабли полиморфизма с++
Посмотрите здесь:

C++ Использование полиморфизма
Модификатор const Очередные грабли с++? C++
C++ Реализация полиморфизма
C++ иллюстрация полиморфизма
Виды полиморфизма C++ C++
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
taras atavin
Ушёл с форума.
 Аватар для taras atavin
3569 / 1752 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
23.11.2011, 12:51     Неочевидные грабли полиморфизма с++ #41
Цитата Сообщение от Bers Посмотреть сообщение
"указатель типа...", ну например: "указатель типа int"
Тип указателя это всегда тип указателя, а не самого данного. Например, указатель типа указателя на int. Или указатель типа int*. Но не просто типа int. Уж если типа int, то само число.
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
fasked
Эксперт C++
 Аватар для fasked
4925 / 2505 / 180
Регистрация: 07.10.2009
Сообщений: 4,306
Записей в блоге: 1
23.11.2011, 12:56     Неочевидные грабли полиморфизма с++ #42
Цитата Сообщение от taras atavin Посмотреть сообщение
а не самого данного
Кто такой "данный"?
Цитата Сообщение от taras atavin Посмотреть сообщение
указатель типа указателя на int. Или указатель типа int*.
Мне в голову приходит сразу двойной указатель.
taras atavin
Ушёл с форума.
 Аватар для taras atavin
3569 / 1752 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
23.11.2011, 13:07     Неочевидные грабли полиморфизма с++ #43
Цитата Сообщение от Bers Посмотреть сообщение
Вот вы и объясните почему?
Потому что не совместимы. Вот представь себе: домну превратить в корабль. Кто начал вспоминать заклятие? Отставить! Не мучайтесь, вы их не знаете. И это реальная Земля, а не вымысел про Сабрину. И так превращение домны в корабль. Сказано отставить магию! Лёгким движением руки домна превращается... Превращается домна... Домна преващается... в элегантные руины! Простите, это другой класс, нужен был корабль.

Добавлено через 1 минуту
Цитата Сообщение от ValeryLaptev Посмотреть сообщение
Нет тут перекрестного преобразования от базового к базовому.
А что тогда такое от A к B?

Добавлено через 7 минут
Цитата Сообщение от Deviaphan Посмотреть сообщение
Соответственно есть "два типа указателей" На объекты и на скаляры.
Во-первых один бит - ещё не конкретика, а во-вторых джавовики умудряются вообще отличать объекты от скаляров. Так что смысловой нагрузки в слове "объект" нет, если только не описывается синтаксис, или понятие.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
23.11.2011, 13:08     Неочевидные грабли полиморфизма с++ #44
Цитата Сообщение от taras atavin Посмотреть сообщение
указатель типа указателя на int. Или указатель типа int*. Но не просто типа int.
int** - указатель типа int*
int* - указатель типа int
int - просто тип инт

Слово "указатель" семантично равнозначно добавлению * к названию типа. Т.е. "тип int*" и "указатель на тип int" это одно и то же.
taras atavin
Ушёл с форума.
 Аватар для taras atavin
3569 / 1752 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
23.11.2011, 13:08     Неочевидные грабли полиморфизма с++ #45
Цитата Сообщение от fasked Посмотреть сообщение
Кто такой "данный"?
данное, а не данный.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
23.11.2011, 13:10     Неочевидные грабли полиморфизма с++ #46
Цитата Сообщение от taras atavin Посмотреть сообщение
джавовики умудряются вообще отличать объекты от скаляров
А в С++ объект это область памяти. Скаляр тоже "область памяти". Для скаляров можно вызывать конструктор с параметрами. В ООП делается всё возможное, чтобы встроенные типы считались объектами.
taras atavin
Ушёл с форума.
 Аватар для taras atavin
3569 / 1752 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
23.11.2011, 13:18     Неочевидные грабли полиморфизма с++ #47
Цитата Сообщение от Deviaphan Посмотреть сообщение
"тип int*" и "указатель на тип int" это одно и то же.
Вот именно. Но сам указатель типа int*, а не int.
C++
1
int *p;
Указатель здесь зовут p и это указатель на int. А теперь забыли слово "указатель". Какого типа p? В данном случае p типа int*. И никакого двойного! "p типа int*" - это
C++
1
int *p;
, различие в другом языке (русском вместо плюсов). А теперь вспоминаем, что p - это указатель. Так какого типа указатель? Конечно же типа double+! Нет? Неужели типа float/? Опять нет? Может типа char-?

Добавлено через 36 секунд
Цитата Сообщение от Deviaphan Посмотреть сообщение
джавовики умудряются вообще отличать объекты от скаляров
то есть не отличать.

Добавлено через 1 минуту
Цитата Сообщение от Deviaphan Посмотреть сообщение
Для скаляров можно вызывать конструктор с параметрами.
Ну ка вызови на плюсах. Попробуй также вызвать метод, или обратиться к полю.

Добавлено через 49 секунд
Цитата Сообщение от Deviaphan Посмотреть сообщение
В ООП делается всё возможное, чтобы встроенные типы считались объектами.
Делается, но плюсодевелоперами до конца не сделано.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
23.11.2011, 13:25     Неочевидные грабли полиморфизма с++ #48
Цитата Сообщение от taras atavin Посмотреть сообщение
Ну ка вызови на плюсах.
Вот, конструктор с параметром
C++
1
int a(123);
Про методы я ничего не говорил. Если проигнорировать вызов operator+ как метода, то можно сделать объект, внешне идентичный скаляру.

Впрочем беседа уже свелась к тому, называть фигуру "квадрат" или "равносторонний прямоугольник"...

Добавлено через 28 секунд
Цитата Сообщение от Deviaphan Посмотреть сообщение
int a(123);
или
C++
1
double b = double(3.14);
Bers
Заблокирован
24.11.2011, 09:00  [ТС]     Неочевидные грабли полиморфизма с++ #49
Цитата Сообщение от Deviaphan Посмотреть сообщение
Слово "указатель" семантично равнозначно добавлению * к названию типа. Т.е. "тип int*" и "указатель на тип int" это одно и то же.
Несколько раз перечитывал запись, пытаясь понять.
Всегда считал, что int* это и есть указатель

Сам по себе указатель - это и есть тип.

Другое дело, что переменная типа указатель содержит адрес объекта.
И эту переменную так же называют указателем.
C++
1
2
3
A* ptr = new C; //семантически, A* это объявление указателя типа A
                        //ptr - указатель типа A                        
                        //ptr - это указатель, который смотрит на объект типа C
В чем здесь подвох? Подвох в том, что "ptr указатель на A".
О том, что на самом деле он смотрит на объект типа C знают только программисты, да может быть всякие там динамик_касты.
Компилятор же считает, что указатель_на_тип априори указывает на объекты такого же типа.

Указатель_на_тип - нужно понимать, как тип самого указателя.

По сабжу, есть только два варианта:
1 Либо забить на производительность и юзать динамик_касты.
2 Либо программист сам отдаёт отчет в том, куда в каждый момент времени смотрят указатели, и какие там на самом деле живут объекты. И не допустить срезки.
Самый простой способ добиться такого эффекта - не юзать множественное наследование при полиморфизме. И вообще не делать глубоких/широких наследований. Тогда не придётся втыкать в низко-уровневую работу полиморфизма.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
24.11.2011, 09:13     Неочевидные грабли полиморфизма с++ #50
Цитата Сообщение от Bers Посмотреть сообщение
Несколько раз перечитывал запись, пытаясь понять.
Всегда считал, что int* это и есть указатель
Именно об этом я и написал, да.

Добавлено через 1 минуту
Вернее - нет.)
int* это тип, а переменная этого типа - указатель.

Добавлено через 2 минуты
Цитата Сообщение от Bers Посмотреть сообщение
1 Либо забить на производительность и юзать динамик_касты.
2 Либо программист сам отдаёт отчет в том, куда в каждый момент времени смотрят указатели, и какие там на самом деле живут объекты. И не допустить срезки.
1. Да
2. Нет. Программист не должен об этом думать. Если он должен об этом думать, скорее всего это ошибка проектирования.
Срезка бывает только при использовании значений. При использовании указателей и ссылок срезки не бывает.
Bers
Заблокирован
24.11.2011, 09:23  [ТС]     Неочевидные грабли полиморфизма с++ #51
Цитата Сообщение от Deviaphan Посмотреть сообщение
Срезка бывает только при использовании значений.
Указатель на ЦЕ приводится к указателю на А не для того, что бы хранить указатель на А, и перед использованием привести его обратно к указателю на ЦЕ.

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

А как это вообще получилось? У человека была самодельная фабрика, которая производила объекты типа ЦЕ, а наружу выдавала указатели на А.
И уже снаружи, вызывающая сторона приводила А к чему ей было нужно, ничего не зная о том, какой в действительности был сконструирован объект самой фабрикой.

На мой взгляд, человек допустил этот архитектурный ляп потому что не осознавал как работает полиморфизм на низком уровне. А когда осознал всю неправильность своего творчества, было уже поздно - слишком многое было завязано на такой фабрике. И ему не оставалось ничего иного, кроме как воспользоваться услугами динамик_каст.

Итого: неоправданное падение производительности там, где этого можно было с лёгкостью избежать.


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

Лучше просто не связываться с такими архитектурными сооружениями следуя простому правилу:
1. Избегайте множественного наследования.
2. Избегайте глубокого наследования полиморфных интерфейсов.

А вообще, в с++ действует одно железное правило "ружья и собственной ноги". Оно гласит:

"Можно все! Если знаешь что делаешь. Если не знаешь - тогда нельзя"
fasked
Эксперт C++
 Аватар для fasked
4925 / 2505 / 180
Регистрация: 07.10.2009
Сообщений: 4,306
Записей в блоге: 1
24.11.2011, 10:39     Неочевидные грабли полиморфизма с++ #52
Цитата Сообщение от Bers Посмотреть сообщение
Лучше просто не связываться с такими архитектурными сооружениями следуя простому правилу:
1. Избегайте множественного наследования.
2. Избегайте глубокого наследования полиморфных интерфейсов.
Как об стенку горох. Тут осталось только одно сказать: "Вы просто не умеете их готовить"
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
24.11.2011, 10:43     Неочевидные грабли полиморфизма с++ #53
Цитата Сообщение от Bers Посмотреть сообщение
по сабжу, именно поэтому и происходила срезка
Там НЕТУ срезки. Перечитай приведённую тобой ссылку более внимательно. При срезке данные ТЕРЯЮТСЯ, в твоём примере ничего не теряется. "Срезанный" объект уже невозможно вернуть к исходному состоянию. Повторяю, у тебя НЕТ срезки. Срезка бывает только при использовании объектов по значению. При передаче через указатель срезки не бывает.


Цитата Сообщение от Bers Посмотреть сообщение
У человека была самодельная фабрика, которая производила объекты типа ЦЕ, а наружу выдавала указатели на А.
И уже снаружи, вызывающая сторона приводила А к чему ей было нужно, ничего не зная о том, какой в действительности был сконструирован объект самой фабрикой
У человека была ошибочно спроектирована архитектура проекта. Смысл абстрактных фабрик именно в том, чтобы не заморачиваться на счёт действительного типа объекта...


Цитата Сообщение от Bers Посмотреть сообщение
Итого: неоправданное падение производительности там, где этого можно было с лёгкостью избежать.
Именно так. Но падение производительности сперва нужно доказать.)


Цитата Сообщение от Bers Посмотреть сообщение
С другой стороны, архитектурные сооружения, которые вынуждают проектировщиков высокого уровня знать о том, как механизмы (наследования, полиморфизма) работают на низком уровне - это попахивает нарушением инкапсуляции.
Ничего подобного. Не пиши "гнилой" код и ничего низкоуровневого знать не нужно. Более того, знать о том, как работают механизмы наследования в общем случае невозможно. Как я уже написал несколько раз выше, реализация полиморфизма не стандартизирована. Важно лишь поведение, которое стандартизировано. Кривые руки и невменяемые программисты стандартом не регламентируются.

Добавлено через 56 секунд
Цитата Сообщение от Bers Посмотреть сообщение
Избегайте множественного наследования.
...в тех ситуациях, когда множественное наследование не нужно.
BRcr
 Аватар для BRcr
4003 / 2292 / 155
Регистрация: 03.02.2011
Сообщений: 5,064
Записей в блоге: 10
24.11.2011, 10:53     Неочевидные грабли полиморфизма с++ #54
Цитата Сообщение от Bers Посмотреть сообщение
1. Избегайте множественного наследования.
2. Избегайте глубокого наследования полиморфных интерфейсов.
Bers, вам что, приз дадут, ежели вы измором возьмете споривших с вами в этой теме?
Не надо нечего и никому избегать, и правила все эти нумерованные тоже никому не нужны...
Просто всегда нужно знать, чего делаешь, и только-то; в этой теме уже предостаточно написано, чтоб разобраться и в приведении типов, и в dynamic_cast, и в наследовании, и в таблицах функций... правила эти ваши зачем?
Bers
Заблокирован
24.11.2011, 10:59  [ТС]     Неочевидные грабли полиморфизма с++ #55
Цитата Сообщение от Deviaphan Посмотреть сообщение
Там НЕТУ срезки.
C++
1
((B*)ptr)->Func2(); //вывод: A::Func1 Почему?
Правильный ответ: потому что срезка объекта.

Добавлено через 2 минуты
Цитата Сообщение от Deviaphan Посмотреть сообщение
Срезка бывает только при использовании объектов по значению.
Обращение к объекту по указателю - работа с самим объектом.
Работать с объектом по указателю == работать с самим объектом по значению.
При этом, если тип указателя и тип объекта, который живет на самом деле по адресу - разные, что будит? Будит срезка, или ещё какие нибудь не очень приятные приколы

Добавлено через 1 минуту
Цитата Сообщение от BRcr Посмотреть сообщение
Bers, вам что, приз дадут, ежели вы измором возьмете споривших с вами в этой теме
Вам показалось. Я ни с кем не спорю.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
24.11.2011, 11:02     Неочевидные грабли полиморфизма с++ #56
Цитата Сообщение от Bers Посмотреть сообщение
Правильный ответ: потому что срезка объекта.
Ответ не верный. Ты просто не понимаешь, что такое срезка.
Почему так происходит я уже разжевал и разложил по полочкам.
То, что ты не желаешь читать - не мои проблемы. Когда надумаешь действительно разобраться в проблеме, то перейди на третью страницу.

Добавлено через 2 минуты
Цитата Сообщение от Bers Посмотреть сообщение
При этом, если тип указателя и тип объекта, который живет на самом деле по адресу - разные, что будит? Будит срезка, или ещё какие нибудь не очень приятные приколы
Это не срезка. Срезка это потеря данных при копировании в процессе преобразования объекта дочернего класса к объекту базового. Т.е. когда копируются не все данные, а только те, которые известны в базовом классе. Так тебе понятнее? При работе по ссылке и указателю никакого копирования не происходит, соответственно и срезки быть не может в принципе.
Ты даже не можешь толком разобраться в примере, ссылку на который привёл. Там объект передаётся по значению, поэтому там и есть срезка.
Bers
Заблокирован
24.11.2011, 11:04  [ТС]     Неочевидные грабли полиморфизма с++ #57
Цитата Сообщение от Deviaphan Посмотреть сообщение
Ответ не верный. Ты просто не понимаешь, что такое срезка
Что такое срезка по вашему?

По-моему, срезка - это когда большой объект приводится (интерпритируется) как более мелкий. И часть данных в итоге улетает в трубу.

В данном случае, компилятор считает что по указанному адресу живет объект типа A
Ну или объект типа B

На самом деле там живет объект типа C, но при обращении к объекту через указатель, компилятор воспринимает объект не как С, а как А или B.

Таким образом, объект, с которым происходит работа через левый указатель, оказывается срезанным.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
24.11.2011, 11:14     Неочевидные грабли полиморфизма с++ #58
Цитата Сообщение от Bers Посмотреть сообщение
По-моему, срезка - это когда большой объект приводится (интерпритируется) как более мелкий.
Приводится и интерпретируется это два абсолютно разных понятия.
Более того, невозможно изменять интерпретацию объектов. Интерпретировать можно лишь указатели.
Что такое приведение объекта? Это вызов копирующего конструктора. Виртуальных конструкторов не бывает, поэтому при копировании(приведении типа) копируются не все данные. Это и есть срезка.
О том, что такое срезка я уже писал выше, поэтому повторю

Цитата Сообщение от Deviaphan Посмотреть сообщение
То, что ты не желаешь читать - не мои проблемы
Добавлено через 2 минуты
Придумал, как объяснить нагляднее!
Смотри, читай, задавай вопросы.
C++
1
2
3
4
5
6
class A;
class B : public A;
 
B b;
A * a1 = &b; // нет срезки
A a2 = b; // есть срезка
Добавлено через 3 минуты
Специально для тебя Дьюхэрст написал книгу "Скользкие места C++".
Глава по "срезке" на странице 77. Если и после этого просветление тебя не посетит, то прекращай использовать наследование в любых формах. Указателями лучше тоже кончай пользоваться.
Bers
Заблокирован
24.11.2011, 11:19  [ТС]     Неочевидные грабли полиморфизма с++ #59
Цитата Сообщение от 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
struct Base
{
    Base(): a(333){}
    Base( const Base& istok) { std::cout << "base\n"; }
    int a;
};
 
 
struct Derrived:public Base
{
    Derrived():Base() {}
    Derrived( const Derrived& istok): Base(istok) { std::cout << "Derrived\n"; }
};
 
 
int main()
{
    
    Derrived t;
    (Base)t; //по мнению Deviaphan  здесь
                 //должен запустится копирующий 
                 //конструктор
     
    return 0;
}

Почему же тогда не запустился копирующий конструктор, а??
Как вы можете это объяснить? Или может быть все таки, вы не правы?

А я вам объясню: объект это просто кучка байтов. Сама по себе не имеющая типа.
О том, как интерпритируется эта кучка байтов знает либо тип переменной, либо тип указателя.

И если тип указателя A, то компилятору пофегу, что на самом деле там живет C
При работе с объектом через такой указатель, компилятор будит считать, что там живет А

Это к вопросу об интерпретации данных.


Привести один тип к другому, не значит скопировать данные. Это значит сказать компилятору: интерпретируй этот объект, как объект другого типа.

При этом, при работе с "приведенным" типом так же может происходить срезка данных. Когда одни данные начинают восприниматься как другие данные, а часть данных вообще будит тупо проигнорирована.

Срезка происходит не когда данные не полностью скопированы, а тогда, когда компилятор уже "потерял" часть объекта.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
24.11.2011, 11:29     Неочевидные грабли полиморфизма с++
Еще ссылки по теме:

В чем смысл полиморфизма C++
C++ Использование свойств полиморфизма
Смысл использования полиморфизма C++

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

Или воспользуйтесь поиском по форуму:
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
24.11.2011, 11:29     Неочевидные грабли полиморфизма с++ #60
Цитата Сообщение от Bers Посмотреть сообщение
Это к вопросу об интерпретации данных.
Почему ты думаешь, что строки 20-21 вообще есть в коде?
Интерпретация данных, это reinterpret_cast, вызвать для не скалярных типов нельзя.
Приведение типов static_cast - создание копии.
Интерпретация и приведение типа это не одно и то же.
Если строка 21 отработает, то произойдёт срезка t.
Yandex
Объявления
24.11.2011, 11:29     Неочевидные грабли полиморфизма с++
Ответ Создать тему
Опции темы

Текущее время: 23:40. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru