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

С++ для начинающих

Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 52, средняя оценка - 4.79
Bers
Заблокирован
#1

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

23.11.2011, 01:05. Просмотров 6905. Ответов 100
Метки нет (Все метки)

Наткнулся в интернете на любопытный код.
Спешу поделиться с сообществом.
Просто, что бы кто если не в курсе - узнал, и не попал на эти грабли:


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     Неочевидные грабли полиморфизма с++
Посмотрите здесь:

Модификатор const Очередные грабли с++? - C++
Представленный ниже код не компилируется. В чем здесь может быть проблема? class CFirst { public: int GetValue() { return...

Использование полиморфизма - C++
Помогите написать программу, которая использует перегрузительную функцию для работы с данными типов long и double и определяет...

иллюстрация полиморфизма - C++
Доброго времени суток!написал примитив для иллюстрации полиморфизма,ориентировался по видеокурсам с ТыТрубы #include&lt;iostream.h&gt; ...

Реализация полиморфизма - C++
Читал что существует примерно 10 способов реализации полиморфного контейнера. Видел только один где создается виртуальный класс и у него...

Виды полиморфизма C++ - C++
Разбираю полиморфизм. Наткнулся на классификацию с тремя видами:1.специальный, 2.параметрический и 3.подтипов(включения). Все ли...

Принципы наследования и полиморфизма - C++
Даны натуральное число n, действительные числа a1 a2,...,an. Если последовательность a1 ,a2 ,...,an упорядочена по неубыванию, то...

Смысл использования полиморфизма - C++
#include &lt;iostream&gt; using namespace std; class A{ public: virtual void speak() {} };

После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Bers
Заблокирован
23.11.2011, 10:39  [ТС]     Неочевидные грабли полиморфизма с++ #16
Цитата Сообщение от ValeryLaptev Посмотреть сообщение
3. Простое преобразование типа указателя не прокатывает при вызове виртуальных функций. Именно поэтому и был придуман dynamic_cast<>.
Вот вы и объясните почему?


Цитата Сообщение от ValeryLaptev Посмотреть сообщение
4. Вы пытаетесь преобразовать указатель типа A в указатель типа В, который никаким боком с А не связан.
Связаны общим потомком. Именно поэтому динамическое кастование корректно

Добавлено через 50 секунд
Мой тезис только подтверждаеццо: полиморфизм + множественное наследование = мина замедленного действия.
ValeryLaptev
Эксперт С++
1039 / 818 / 48
Регистрация: 30.04.2011
Сообщений: 1,659
23.11.2011, 10:42     Неочевидные грабли полиморфизма с++ #17
Bers,
Преобразования допускаются только между «родственниками», то есть классами, входящими в одну иерархию наследования (тип указателя в круглых скобках должен быть родственным типу в угловых скобках). Преобразование может быть:
повышающим — от производного класса к базовому;
понижающим — от базового класса к производному;
перекрестным — от одного производного класса к другому.
Вот же - написано.
Нет тут перекрестного преобразования от базового к базовому.
Bers
Заблокирован
23.11.2011, 10:46  [ТС]     Неочевидные грабли полиморфизма с++ #18
Цитата Сообщение от ValeryLaptev Посмотреть сообщение
Вот же - написано.
Нет тут перекрестного преобразования от базового к базовому.
Меня не интересует, есть там перекрестки, или нет. Меня интересует, почему динамик_каст корректно приводит, а приведение в стиле си - не корректно.
И что происходит с объектом, в момент приведения его типа к одному из базовых (срезка).

По русски, с толком, с расстановкой. Не?
Я вам намекну, динамик_каст - дорогое приведение, потому что юзает технологию определения типа объекта в рантайме.
fasked
Эксперт С++
4933 / 2513 / 180
Регистрация: 07.10.2009
Сообщений: 4,311
Записей в блоге: 1
23.11.2011, 10:46     Неочевидные грабли полиморфизма с++ #19
Извините меня все же, но таки в каком месте здесь грабли неочевидные и камни подводные?
Bers
Заблокирован
23.11.2011, 10:47  [ТС]     Неочевидные грабли полиморфизма с++ #20
не хотите терять производительность - не юзайте динамик_каст. А значит, не юзайте множественное наследование с полиморфизмом. Ваш К. О.
ValeryLaptev
Эксперт С++
1039 / 818 / 48
Регистрация: 30.04.2011
Сообщений: 1,659
23.11.2011, 10:49     Неочевидные грабли полиморфизма с++ #21
Цитата Сообщение от Bers Посмотреть сообщение
Вот вы и объясните почему?
Спустимся немного вглубь.
Все указатели на уровне реализации - абсолютно одинаковы. Поэтому явное преобразование типа указателя - это просто для порядку на уровне С++. На самом деле никаких преобразований не производится. Как и было в С.
А вот при наличии в классе виртуальных функциях нужно знать тип ВО ВРЕМЯ ВЫПОЛНЕНИЯ. Чтобы вызвать нужную функцию. Это довольно сложная операция. Это и есть механизм RTTI.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1286 / 1220 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
23.11.2011, 10:50     Неочевидные грабли полиморфизма с++ #22
Сообщение было отмечено автором темы, экспертом или модератором как ответ
Цитата Сообщение от Bers Посмотреть сообщение
Вот вы и объясните почему?
Позволь я объясню. Но это объяснение подходит только для компилятора MSVC, т.к. реализация виртуальных функций никак не регламентирована и реализуется разработчиками по своему усмотрению.
Итак MSVC:
В начале каждого объекта, содержащего виртуальные функции, записывается адрес используемой им таблицы виртуальных функций. Адрес объекта совпадает с указателем на эту таблицу. Виртуальные функции добавляются в эту таблицу в том же порядке, в котором они объявлены в классе. Вызов происходит не напрямую по адресу, а через индекс этой функции. Т.е. вместо непосредственного использования адреса функции, происходит индексированный доступ к таблице и вызов функции по соответствующему адресу.
При множественном наследовании всё усложняется. Для первого базового класса указатель по прежнему записан в самом начале, затем идут данные-члены этого класса и только потом указатель на таблицу второго базового класса. Когда ты выполняешь приведение типа с помощью dynamic_cast, адрес объекта изменяется, т.к. он переносится на "начало" объекта второго базового класса. Ты можешь это легко проверить, выводя адрес объекта после кастования.
Используя reinterpret_cast (круглые скобки это он и есть в данном случае), ты интерпретируешь указатель на один объект, как указатель на другой. Программа бы могла упасть, но прототипы функций одинаковые, поэтому порчи стека не происходит. Данные объекта тоже не используются. Но происходит индексированый доступ функции. Что ты и видишь в консоли.
Можешь добавить в классы поля и попробывать их выводить в консоль (но не изменять) и зразу увидишь, что там не то, что ты ожидаешь.

И это не грабли полиморфизма, это некорректная программа. Не зря ведь говорят ЗАБУДЬТЕ ПРО ПРИВЕДЕНИЕ ТИПОВ В СТИЛЕ Си.
ValeryLaptev
Эксперт С++
1039 / 818 / 48
Регистрация: 30.04.2011
Сообщений: 1,659
23.11.2011, 10:50     Неочевидные грабли полиморфизма с++ #23
Цитата Сообщение от Bers Посмотреть сообщение
Меня не интересует, есть там перекрестки, или нет. Меня интересует, почему динамик_каст корректно приводит, а приведение в стиле си - не корректно.
И что происходит с объектом, в момент приведения его типа к одному из базовых (срезка).

По русски, с толком, с расстановкой. Не?
Я вам намекну, динамик_каст - дорогое приведение, потому что юзает технологию определения типа объекта в рантайме.
Дык в С - не было классов! И тем более - не было виртуальных функций. И наследования.
Потому и не приводит. Обратная же совместимость!
Bers
Заблокирован
23.11.2011, 10:51  [ТС]     Неочевидные грабли полиморфизма с++ #24
Спустимся:
C++
1
2
aPtr->Func1(); //вывод: A::Func1
 bPtr->Func2(); //вывод: B::Func2

Корректно.

Вы по прежнему щитаете, что между ними нет связи?
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1286 / 1220 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
23.11.2011, 10:53     Неочевидные грабли полиморфизма с++ #25
Цитата Сообщение от Bers Посмотреть сообщение
И что происходит с объектом, в момент приведения его типа к одному из базовых (срезка).
Именно срезка. Но только при приведении типа объекта. При приведении указателя на объект к указателю на базовый тип никакой срезки не происходит. И при ссылке срезки тоже не происходит.
fasked
Эксперт С++
4933 / 2513 / 180
Регистрация: 07.10.2009
Сообщений: 4,311
Записей в блоге: 1
23.11.2011, 10:54     Неочевидные грабли полиморфизма с++ #26
В общем-то насколько мне известно, множественный полиморфизм может быть реализован с использованием нескольких таблиц виртуальных методов (в данном случае двух). Собственно, как выше и говорилось, для этого и нужен dynamic_cast. Если использовать преобразование в стиле Си, то указатель vtable получается фактически случайным из двух.
ValeryLaptev
Эксперт С++
1039 / 818 / 48
Регистрация: 30.04.2011
Сообщений: 1,659
23.11.2011, 10:55     Неочевидные грабли полиморфизма с++ #27
Цитата Сообщение от Bers Посмотреть сообщение
Спустимся:
C++
1
2
aPtr->Func1(); //вывод: A::Func1
 bPtr->Func2(); //вывод: B::Func2
Корректно.

Вы по прежнему щитаете, что между ними нет связи?
А и В вообще разными программистами в разных файлах могли быть написаны. И транслируются в разное время. Один вчера, а другой - сегодня. ГДЕ тут связь?
Откуда компилятор мог знать, что Берс возьмет эти два класса и множественно унаследует.
А потом явные имена при вызове будет писать и требовать "продолжения банкета" с виртуальными функциями.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1286 / 1220 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
23.11.2011, 10:55     Неочевидные грабли полиморфизма с++ #28
Цитата Сообщение от Bers Посмотреть сообщение
Вы по прежнему щитаете, что между ними нет связи?
Между А и В связи нет.
Но!
Если А* и В* на самом деле указывают на объект типа С, то связь есть. Но только в случае использования dynamic_cast. При желании, можно вручную рассчитать необходимое смещение, сдвинуть указатель и поменять его тип. Но такому желанию лучше не возникать.)
Bers
Заблокирован
23.11.2011, 11:02  [ТС]     Неочевидные грабли полиморфизма с++ #29
ну так вот, возвращаясь к моему тезису:
1. Избегайте множественного наследования + полиморфизм.
2. Избегайте динамического кастования, если дорога производительность.

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

Использование свойств полиморфизма - C++
Нужна помощь. Сгенерируйте абстрактный класс типа фигура, создайте производные от него классы типа пятиугольник, прямоугольник. В классах...

В чем смысл полиморфизма - C++
Объясните, пожалуйста, смысл полиморфизма. Не могу никак вникнуть. Где и как он используется? И примерчик, пожалуйста

Понятия инкапсуляции, полиморфизма и наследования - C++
Всем привет. Прошу прояснить для себя 3 основных свойства парадигмы ООП инкапсуляцию, наследования и полиморфизм. Я напишу своё видение и...

Использование свойства полиморфизма и исследование механизма виртуальных функций - C++
Создайте класс Matr, определите в нем виртуальную функцию-член matrica(int n), которая определяет произведение элементов квадратной...

Создать базовый класс, использовать свойства полиморфизма и абстракции - C++
Создать базовый класс &quot; Транспортное средство&quot; и производные классы: &quot;Автомобиль&quot;, &quot;Мопед&quot;, &quot;Велосипед&quot;. Посчитать время и стоимость...


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

Или воспользуйтесь поиском по форуму:
Deviaphan
Делаю внезапно и красиво
Эксперт C++
1286 / 1220 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
23.11.2011, 11:10     Неочевидные грабли полиморфизма с++ #30
Цитата Сообщение от Bers Посмотреть сообщение
ну так вот, возвращаясь к моему тезису:
1. Избегайте множественного наследования + полиморфизм.
2. Избегайте динамического кастования, если дорога производительность.
В противном случае не удивляйтесь ничайному выстрелу себе в ногу.
1 - Нет!
2 - Это миф.
3 - Учитесь программировать и не придётся искать достаточно длинную верёвку, чтобы выстрелить себе в ногу.
Yandex
Объявления
23.11.2011, 11:10     Неочевидные грабли полиморфизма с++
Ответ Создать тему
Опции темы

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