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

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

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

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

23.11.2011, 01:05. Просмотров 7158. Ответов 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
Лучшие ответы (1)
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
23.11.2011, 01:05
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Неочевидные грабли полиморфизма с++ (C++):

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

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

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

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

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

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

100
Bers
Заблокирован
23.11.2011, 11:11  [ТС] #31
сказал Deviaphan, ничерта не пролив свет на происходящее
0
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
23.11.2011, 11:18 #32
Цитата Сообщение от Bers Посмотреть сообщение
ничерта не пролив свет на происходящее
На странице ТРИ я в подробности описал, как работает полиморфизм в С++. Если не желаешь читать, это не мои траблы.

Добавлено через 32 секунды
От тута Неочевидные грабли полиморфизма с++
0
ForEveR
В астрале
Эксперт С++
7983 / 4742 / 321
Регистрация: 24.06.2010
Сообщений: 10,545
Завершенные тесты: 3
23.11.2011, 11:41 #33
Deviaphan, На счет динамик каста я бы поспорил. Много динамик кастов убивают производительность. В одной проге профайлер показывает, что динамик каст занимает 14% общего времени, правда используется он там действительно немало где
0
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
23.11.2011, 11:51 #34
Цитата Сообщение от ForEveR Посмотреть сообщение
В одной проге профайлер показывает, что динамик каст занимает 14% общего времени
Скорее всего ошибка в проектировании. В 80% случаев, повышающий каст означает ошибку проектирования. В 20% программисту удаётся доказать, что это не ошибка.)

И маленький примерчик, для "закрепления материала"
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
class A
{
public:
    virtual void Func1(){cout<<"A::Func1"<<endl;}
};
 
class B
{ 
public:
    virtual void Func2(){cout<<"B::Func2"<<endl;}
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    A * a = new A;
    A * b = (A*)(new B);
 
    a->Func1();
    b->Func1();
 
 
    cin.get();
    return 0;
}
Добавлено через 1 минуту
Можно закомментировать слова virtual, чтобы совсем уж точно материал усвоить.)
2
taras atavin
3570 / 1753 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
23.11.2011, 11:54 #35
Цитата Сообщение от Bers Посмотреть сообщение
#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;
}
Не вижу граблей, а вот если бы компилятор проглотил void*, то это и были бы грабли.
0
ForEveR
23.11.2011, 11:56
  #36

Не по теме:

Deviaphan, http://www.jezuk.co.uk/cgi-bin/view/arabica/howtobuild при желании можешь посмотреть сорцы. Мб это и ошибка в проектировании

0
taras atavin
3570 / 1753 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
23.11.2011, 12:01 #37
Цитата Сообщение от Сыроежка Посмотреть сообщение
вывод: A::Func1 Почему?
Потому что это не потомок A. Ты тупо переназначил тип самому адресу, то есть сказал машине, что адрес теперь просто целое и сразу: "Нет, я передумал, это адрес B", и вызвал функцию по определённому смещению в таблице виртуальных функций, но таблица то по этому указателю соответствует классу A и по такому смещению там валяется указатель на функцию A::Func1().
C++
1
2
float x;
std::cout<<*((int*)(&x)); // Вывод бредовый.  Грабли? Нет. Так просто не делают, если только не хотят специально вывести в десятичном формате код икса, а не его значение.
Автор твоих "не очевидных граблей" просто считает всех вокруг идиотами, не способными понять смысл своих действий.
0
Deviaphan
23.11.2011, 12:04
  #38

Не по теме:

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

1
taras atavin
3570 / 1753 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
23.11.2011, 12:46 #39
Цитата Сообщение от Bers Посмотреть сообщение
Ни о чем таком не говорит?
Это говорит о наследовании как таковом, а множественное - это когда и указатели на два разных базовых класса могут указывать на одного и того же потомка, но не друг на друга, то есть когда одновременно:
C++
1
2
3
4
A *p1=new B; // не допустимо
B *p2=new A; // не допустимо
A *p3=new C; // допустимо
B *p4=new C; // допустимо
. У тебя и это есть, а полиморфизм - это вообще возможность перекрывать методы предка, что также имеется в приведённом исходнике.

Добавлено через 7 минут
Цитата Сообщение от Bers Посмотреть сообщение
И потом, класс C здесь - это самый дальний потомок.
О какой дальности может идти речь при одном уровне наследования? C - это ближайший потомок из всех возможных, а здесь - единственный.

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

Добавлено через 1 минуту
Цитата Сообщение от taras atavin Посмотреть сообщение
классу A
Точнее классу C, но начало таблицы класса C совпадает с таблицей класса A.

Добавлено через 9 минут
Цитата Сообщение от Bers Посмотреть сообщение
Указатель может быть типа int.
Указателей на int не бывает
наоборот.
C++
1
2
int x;
int *p;
x типа int, но x не указатель, p указатель, но p не типа int. Как раз p и есть указатель на int. "Указатель на тип", "указатель на класс" означает, что:
1. Это указатель.
2. Ему разрешено указывать только на данные определённого типа, или только на на данные определённого класса - того типа/класса, который поминается в словосочетании.
Чтоб не сбивать тебя с толку следовало бы говорить "указатель на данное типа", "указатель на объект класса", но ни кто так длинно не говорит, все сокращают и все понимают, что на саму абстракцию указатель не указывает.

Добавлено через 3 минуты
Цитата Сообщение от Bers Посмотреть сообщение
объект типа int
Не бывает объектов типа int. int не класс, а скалярный тип, переменные этого типа - не объекты.

Добавлено через 3 минуты
Цитата Сообщение от Bers Посмотреть сообщение
Потому что между типом самого указателя, и типом объекта, куда он смотрит жёсткой связи не существует.
И куда ж она делась то? У всех она есть, только в твоей голове как то проделитилась. Может потому у тебя и падает всё, что с указателями, что ты не отличаешь int* от void*?

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

Добавлено через 6 минут
Цитата Сообщение от Bers Посмотреть сообщение
А вот указатель на объект, или функцию, или функцию-член - звучит однозначно.
Как раз неопределённо. Указатель на объект. На какой, простите, объект? На дом? На корабль? На кирпич? А может на домну? Или на пользователя? Прямо так, на настоящего пользователя в реале? А может на машинное представление данных об объекте? Эйси, всегда это подразумевается. Но о каком объекте? О Солнечной Системе? Или о пылинке? А может о стене? Пока не указан тип, не понятно о каком указателе речь, поэтому "указатель на объект" семантически идентично "указатель", прямо так, одним словом. Слово "объект" просто лишнее, оно не несёт ни какой смысловой нагрузки, так как его абстракция запредельна и не лезет ни в какие рамки, кроме как в тексте на метаязыке о самом понятии "объект" и прибамбасах для поддержки объектной ориентированности. И только когда указан класс, объект принимает хоть какие то разумные очертания, но не раньше.
0
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
23.11.2011, 12:51 #40
Цитата Сообщение от taras atavin Посмотреть сообщение
поэтому "указатель на объект" семантически идентично "указатель"
Противоречишь сам себе. Т.к.
Цитата Сообщение от taras atavin Посмотреть сообщение
Не бывает объектов типа int. int не класс, а скалярный тип, переменные этого типа - не объекты.
Соответственно есть "два типа указателей" На объекты и на скаляры. А ещё есть указатели на массивы (они же на строки). Поэтому уточнение на что конкретно указывает указатель может быть не лишним.
Надо же было твой пост разбить на два.
1
taras atavin
3570 / 1753 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
23.11.2011, 12:51 #41
Цитата Сообщение от Bers Посмотреть сообщение
"указатель типа...", ну например: "указатель типа int"
Тип указателя это всегда тип указателя, а не самого данного. Например, указатель типа указателя на int. Или указатель типа int*. Но не просто типа int. Уж если типа int, то само число.
0
fasked
Эксперт С++
4948 / 2528 / 180
Регистрация: 07.10.2009
Сообщений: 4,311
Записей в блоге: 1
23.11.2011, 12:56 #42
Цитата Сообщение от taras atavin Посмотреть сообщение
а не самого данного
Кто такой "данный"?
Цитата Сообщение от taras atavin Посмотреть сообщение
указатель типа указателя на int. Или указатель типа int*.
Мне в голову приходит сразу двойной указатель.
0
taras atavin
3570 / 1753 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
23.11.2011, 13:07 #43
Цитата Сообщение от Bers Посмотреть сообщение
Вот вы и объясните почему?
Потому что не совместимы. Вот представь себе: домну превратить в корабль. Кто начал вспоминать заклятие? Отставить! Не мучайтесь, вы их не знаете. И это реальная Земля, а не вымысел про Сабрину. И так превращение домны в корабль. Сказано отставить магию! Лёгким движением руки домна превращается... Превращается домна... Домна преващается... в элегантные руины! Простите, это другой класс, нужен был корабль.

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

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

Слово "указатель" семантично равнозначно добавлению * к названию типа. Т.е. "тип int*" и "указатель на тип int" это одно и то же.
0
taras atavin
3570 / 1753 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
23.11.2011, 13:08 #45
Цитата Сообщение от fasked Посмотреть сообщение
Кто такой "данный"?
данное, а не данный.
0
23.11.2011, 13:08
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
23.11.2011, 13:08
Привет! Вот еще темы с ответами:

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

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

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

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


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

Или воспользуйтесь поиском по форуму:
45
Ответ Создать тему
Опции темы

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