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

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

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

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

23.11.2011, 01:05. Просмотров 7156. Ответов 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
Заблокирован
24.11.2011, 11:34  [ТС] #61
Цитата Сообщение от Deviaphan Посмотреть сообщение
Почему ты думаешь, что строки 20-21 вообще есть в коде?
Глуповатый вопрос: почему строки написанного кода присутствуют в коде?

Цитата Сообщение от Deviaphan Посмотреть сообщение
Приведение типов static_cast - создание копии.

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;
    static_cast<Base>(t); //по мнению господина Deviaphan
                            //здесь должен запустится копирующий конструктор
                        //как может объяснить господин тот факт,
                   // что никакого конструктора не запускается?
    return 0;
};
0
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
24.11.2011, 11:42 #62
Придумал поясняющий пример!
C++
1
2
3
4
5
6
7
8
9
10
int a = 123;
double b = 3.1415;
 
int c = (int)b; // приведение типа
//int c = static_cast<int>(b);
double d = (double)a; // тоже приведение
//double c = static_cast<double>(a);
 
int e = *((int*)&b); // интерпретация указателя на double, как указателя на int. Или приведение типа указателя. Присутствует "срезка".
// int e = *reinterpret_cast<int*>(&b);
Добавлено через 4 минуты
Цитата Сообщение от Bers Посмотреть сообщение
//по мнению господина Deviaphan //здесь должен запустится копирующий конструктор //как может объяснить господин тот факт, // что никакого конструктора не запускается?
Запускается. Специально для тебя проверил. В консоли написано "base".

Добавлено через 1 минуту
Можешь убедиться
http://codepad.org/wOdn0CEx
0
Сыроежка
Заблокирован
24.11.2011, 11:45 #63
Цитата Сообщение от Bers Посмотреть сообщение
Код
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}


Почему же тогда не запустился копирующий конструктор, а??
Как вы можете это объяснить?
Вы заблуждаетесь. В вашем примере в выражении (Base)t; создается неименованный временный объект посредством вызова конструктора копирования

C++
1
    Base( const Base& istok) { std::cout << "base\n"; }
Что это означает? Это означает, что в стеке выделяется новый участок памяти для этого объекта. Исходный объект остался в своей памяти совершенно без изменения. То есть теперь у вас два объекта. Правда время жизни нового объекта заканчивается в том же предложении, где он был и сооздан. Но вы можете создать константную ссылку на него и тогда объект будет продолжать жить пока ссылка не выйдет из области своей видимости:

C++
1
const Base &rb = (Base)t;
То есть никакой речи об "интерпретации" памяти здесь не идет. Здесь имеет место быть два отдельных объекта.
0
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
24.11.2011, 11:49 #64
Цитата Сообщение от Bers Посмотреть сообщение
Глуповатый вопрос: почему строки написанного кода присутствуют в коде?
А ты глянь на ассемблерный листинг. Нет ни создания объекта t, ни привидения типа. Есть только код помещения текста в консоль.)

Добавлено через 58 секунд
Цитата Сообщение от Сыроежка Посмотреть сообщение
То есть никакой речи об "интерпретации" памяти здесь не идет. Здесь имеет место быть два отдельных объекта.
Причём ссылка происходит на временный объект, которого в следующей строке уже нет.

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

Добавлено через 1 минуту
Цитата Сообщение от Сыроежка Посмотреть сообщение
Но вы можете создать константную ссылку на него
Это работает для аргументов функций, т.к. временный объект разрушается только после возврата из функции.
0
Bers
Заблокирован
24.11.2011, 11:50  [ТС] #65
Все, я кажется понял.

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
struct Base
{
    Base(): a(333){}
    Base( const Base& istok) { std::cout << "Copy base\n"; }
    void View(){ std::cout << "base\n"; }
    int a;
};
 
 
struct Derrived:public Base
{
    Derrived():Base() {}
    Derrived( const Derrived& istok): Base(istok) { std::cout << "Copy Derrived\n"; }
    void View(){ std::cout << "Derrived\n"; }
};
 
 
int main()
{
    Derrived t;
    t.View(); //вывод: Derrived
 
    std::cout << "*******\n";
 
    (static_cast<Base>(t)).View(); //вывод: Copy base а потом base
 
    //((Base) t).View(); //вывод: Copy base а потом base
    return 0;
};
В предыдущих примерах-иллюстрациях копирующий конструктор не запускался, потому что не было нужны в том, что бы использовать приведенный объект по назначению.

А так, на самом деле приведение одного типа к другому - действительно запускает копирующий конструктор. Господин Deviaphan совершенно прав.
0
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
24.11.2011, 11:54 #66
Ура-ура, можно перестать биться головой об стенку.)

Теперь попробуй в этом примере заменить static_cast на reinterpret_cast и убедись, что интерпретация не скалярного типа невозможна.
0
Сыроежка
Заблокирован
24.11.2011, 11:54 #67
Цитата Сообщение от Deviaphan Посмотреть сообщение
Добавлено через 1 минуту

Объекта уже не будет. Ссылка битая. Равнозначна указателю на мусор.
Уважаемый, не надо меня поправлять, тем более говоря всякую глупость!

И сам объект и ссылка на него будут жить до тех пор пока ссылка находится в области своей видимости. Так как она объявлена в теле main, то и будет жить в теле этой функции, пока та не завершит свою работу.
0
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
24.11.2011, 11:57 #68
Цитата Сообщение от Bers Посмотреть сообщение
В предыдущих примерах-иллюстрациях копирующий конструктор не запускался, потому что
Не корректный компилятор или настройки. codepad и MSVC (причём в релизе!) копирующий конструктор всё равно запускали. Вывод в консоль это тоже код, который нельзя игнорировать.
Пользуйтесь правильными компиляторами.

Добавлено через 1 минуту
Цитата Сообщение от Сыроежка Посмотреть сообщение
то и будет жить в теле этой функции
Ссылка - да. Но временный объект уже будет разрушен. Можешь проверить.

Добавлено через 38 секунд
Цитата Сообщение от Сыроежка Посмотреть сообщение
И сам объект и ссылка на него будут жить до тех пор пока ссылка находится в области своей видимости.
Интеллектуальных ссылок не бывает.
0
Сыроежка
Заблокирован
24.11.2011, 12:01 #69
Цитата Сообщение от Deviaphan Посмотреть сообщение
Не корректный компилятор или настройки. codepad и MSVC (причём в релизе!) копирующий конструктор всё равно запускали. Вывод в консоль это тоже код, который нельзя игнорировать.
Пользуйтесь правильными компиляторами.

Добавлено через 1 минуту

Ссылка - да. Но временный объект уже будет разрушен. Можешь проверить.

Добавлено через 38 секунд

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

Вам еще рано меня поправлять. Вам следует сначала глубже изучить С++! Несете всякую чушь, да еще с таким пафосом.

Надеюсь, третий раз вы эту чушь нести не будете. Если все же есть зуд, то сначала снова перечитайте, что я пишу. А если этого будет мало, то проверьте на примере.
0
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
24.11.2011, 12:06 #70
Цитата Сообщение от Сыроежка Посмотреть сообщение
Вам следует сначала глубже изучить С++!
Да, ты прав. Этот пункт стандарта упустил.
0
Bers
Заблокирован
24.11.2011, 12:09  [ТС] #71
Цитата Сообщение от 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
struct Base
{
    Base(): a(333){}
    Base( const Base& istok) { std::cout << "Copy base\n"; }
    void View(){ std::cout << "base\n"; }
    int a;
};
 
 
struct Derrived:public Base
{
    Derrived():Base() {}
    Derrived( const Derrived& istok): Base(istok) { std::cout << "Copy Derrived\n"; }
    void View(){ std::cout << "Derrived\n"; }
};
 
 
int main()
{
    Derrived t;
    t.View(); //вывод: Derrived
    std::cout << "*******\n";
 
    Derrived* ptr = new Derrived;
 
    (static_cast<Base*>(ptr))->View();
    
    return 0;
};
0
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
24.11.2011, 12:10 #72
Цитата Сообщение от ForEveR Посмотреть сообщение
Вы не правы.
Как не печально, но это косяк компилятора.)
У Саттера написано, что компилятор "продлевает" время жизни переменных, на которые есть константная ссылка. Но в стандарте пункт я пока не нашёл соответствующий.

Добавлено через 1 минуту
Цитата Сообщение от Bers Посмотреть сообщение
То есть... через указатель я могу интерпритировать тип, избежав копирования.
Потому что интерпретация и приведение типа это не одно и то же. Срезка происходит только при приведении типа.
0
ForEveR
В астрале
Эксперт С++
7983 / 4742 / 321
Регистрация: 24.06.2010
Сообщений: 10,545
Завершенные тесты: 3
24.11.2011, 12:11 #73
Deviaphan, Это не косяк компилятора был) Там в конструкторе копирования ничего не делалось
0
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
24.11.2011, 12:11 #74
Цитата Сообщение от Bers Посмотреть сообщение
избежав копирования
Сейчас ты изменяешь не тип объекта, а тип указателя. Происходит копирование указателя.
0
Сыроежка
Заблокирован
24.11.2011, 12:15 #75
Цитата Сообщение от Deviaphan Посмотреть сообщение
Да, ты прав. Этот пункт стандарта упустил.
Вот вам элементарный пример, чтобы вы больше свою глупость не повторяли

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct A
{
   A( int i = 0 ) { p = new int( i ); }
   A( const A &rhs ) { p = new int( *rhs.p ); }
   ~A() { delete p; }
   int *p;
};
 
 
int main()
{
   const A &ra = A( 10 );
   A a1( 20 );
   
   std::cout << "*ra.p = " << *ra.p << std::endl;
}
0
24.11.2011, 12:15
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
24.11.2011, 12:15
Привет! Вот еще темы с ответами:

Использование свойств полиморфизма - 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 основных свойства парадигмы ООП инкапсуляцию, наследования и полиморфизм. Я напишу своё видение и...


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

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

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