Заблокирован
|
||||||
1 | ||||||
Неочевидные грабли полиморфизма с++23.11.2011, 01:05. Показов 13184. Ответов 101
Метки нет (Все метки)
Наткнулся в интернете на любопытный код.
Спешу поделиться с сообществом. Просто, что бы кто если не в курсе - узнал, и не попал на эти грабли:
Но в любом случае, нужно быть предельно осторожным. А по сути, полиморфизм + множественное наследование = мина замедленного действия. Поэтому, при проектировании полиморфных классов, лучше до последнего избегать множественного наследования.
1
|
23.11.2011, 01:05 | |
Ответы с готовыми решениями:
101
Неочевидные особенности выдачи pg таблиц Неочевидные результаты очевидных css-свойств Грабли с кодировкой Xmega грабли |
1069 / 848 / 60
Регистрация: 30.04.2011
Сообщений: 1,659
|
|
23.11.2011, 10:49 | 21 |
Спустимся немного вглубь.
Все указатели на уровне реализации - абсолютно одинаковы. Поэтому явное преобразование типа указателя - это просто для порядку на уровне С++. На самом деле никаких преобразований не производится. Как и было в С. А вот при наличии в классе виртуальных функциях нужно знать тип ВО ВРЕМЯ ВЫПОЛНЕНИЯ. Чтобы вызвать нужную функцию. Это довольно сложная операция. Это и есть механизм RTTI.
0
|
Делаю внезапно и красиво
1313 / 1228 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
|
|
23.11.2011, 10:50 | 22 |
Сообщение было отмечено как решение
Решение
Позволь я объясню. Но это объяснение подходит только для компилятора MSVC, т.к. реализация виртуальных функций никак не регламентирована и реализуется разработчиками по своему усмотрению.
Итак MSVC: В начале каждого объекта, содержащего виртуальные функции, записывается адрес используемой им таблицы виртуальных функций. Адрес объекта совпадает с указателем на эту таблицу. Виртуальные функции добавляются в эту таблицу в том же порядке, в котором они объявлены в классе. Вызов происходит не напрямую по адресу, а через индекс этой функции. Т.е. вместо непосредственного использования адреса функции, происходит индексированный доступ к таблице и вызов функции по соответствующему адресу. При множественном наследовании всё усложняется. Для первого базового класса указатель по прежнему записан в самом начале, затем идут данные-члены этого класса и только потом указатель на таблицу второго базового класса. Когда ты выполняешь приведение типа с помощью dynamic_cast, адрес объекта изменяется, т.к. он переносится на "начало" объекта второго базового класса. Ты можешь это легко проверить, выводя адрес объекта после кастования. Используя reinterpret_cast (круглые скобки это он и есть в данном случае), ты интерпретируешь указатель на один объект, как указатель на другой. Программа бы могла упасть, но прототипы функций одинаковые, поэтому порчи стека не происходит. Данные объекта тоже не используются. Но происходит индексированый доступ функции. Что ты и видишь в консоли. Можешь добавить в классы поля и попробывать их выводить в консоль (но не изменять) и зразу увидишь, что там не то, что ты ожидаешь. И это не грабли полиморфизма, это некорректная программа. Не зря ведь говорят ЗАБУДЬТЕ ПРО ПРИВЕДЕНИЕ ТИПОВ В СТИЛЕ Си.
4
|
1069 / 848 / 60
Регистрация: 30.04.2011
Сообщений: 1,659
|
|
23.11.2011, 10:50 | 23 |
Дык в С - не было классов! И тем более - не было виртуальных функций. И наследования.
Потому и не приводит. Обратная же совместимость!
0
|
Делаю внезапно и красиво
1313 / 1228 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
|
|
23.11.2011, 10:53 | 25 |
Именно срезка. Но только при приведении типа объекта. При приведении указателя на объект к указателю на базовый тип никакой срезки не происходит. И при ссылке срезки тоже не происходит.
1
|
23.11.2011, 10:54 | 26 |
В общем-то насколько мне известно, множественный полиморфизм может быть реализован с использованием нескольких таблиц виртуальных методов (в данном случае двух). Собственно, как выше и говорилось, для этого и нужен dynamic_cast. Если использовать преобразование в стиле Си, то указатель vtable получается фактически случайным из двух.
0
|
1069 / 848 / 60
Регистрация: 30.04.2011
Сообщений: 1,659
|
|
23.11.2011, 10:55 | 27 |
А и В вообще разными программистами в разных файлах могли быть написаны. И транслируются в разное время. Один вчера, а другой - сегодня. ГДЕ тут связь?
Откуда компилятор мог знать, что Берс возьмет эти два класса и множественно унаследует. А потом явные имена при вызове будет писать и требовать "продолжения банкета" с виртуальными функциями.
0
|
Делаю внезапно и красиво
1313 / 1228 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
|
|
23.11.2011, 10:55 | 28 |
Между А и В связи нет.
Но! Если А* и В* на самом деле указывают на объект типа С, то связь есть. Но только в случае использования dynamic_cast. При желании, можно вручную рассчитать необходимое смещение, сдвинуть указатель и поменять его тип. Но такому желанию лучше не возникать.)
0
|
Делаю внезапно и красиво
1313 / 1228 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
|
|
23.11.2011, 11:10 | 30 |
1 - Нет!
2 - Это миф. 3 - Учитесь программировать и не придётся искать достаточно длинную верёвку, чтобы выстрелить себе в ногу.
2
|
Делаю внезапно и красиво
1313 / 1228 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
|
|
23.11.2011, 11:18 | 32 |
На странице ТРИ я в подробности описал, как работает полиморфизм в С++. Если не желаешь читать, это не мои траблы.
Добавлено через 32 секунды От тута Неочевидные грабли полиморфизма с++
0
|
В астрале
8049 / 4806 / 655
Регистрация: 24.06.2010
Сообщений: 10,562
|
|
23.11.2011, 11:41 | 33 |
Deviaphan, На счет динамик каста я бы поспорил. Много динамик кастов убивают производительность. В одной проге профайлер показывает, что динамик каст занимает 14% общего времени, правда используется он там действительно немало где
0
|
Делаю внезапно и красиво
1313 / 1228 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
|
||||||
23.11.2011, 11:51 | 34 | |||||
Скорее всего ошибка в проектировании. В 80% случаев, повышающий каст означает ошибку проектирования. В 20% программисту удаётся доказать, что это не ошибка.)
И маленький примерчик, для "закрепления материала"
Можно закомментировать слова virtual, чтобы совсем уж точно материал усвоить.)
2
|
4226 / 1795 / 211
Регистрация: 24.11.2009
Сообщений: 27,562
|
|
23.11.2011, 11:54 | 35 |
Не вижу граблей, а вот если бы компилятор проглотил void*, то это и были бы грабли.
0
|
ForEveR
|
23.11.2011, 11:56
#36
|
Не по теме: Deviaphan, http://www.jezuk.co.uk/cgi-bin... howtobuild при желании можешь посмотреть сорцы. Мб это и ошибка в проектировании
0
|
4226 / 1795 / 211
Регистрация: 24.11.2009
Сообщений: 27,562
|
||||||
23.11.2011, 12:01 | 37 | |||||
Потому что это не потомок A. Ты тупо переназначил тип самому адресу, то есть сказал машине, что адрес теперь просто целое и сразу: "Нет, я передумал, это адрес B", и вызвал функцию по определённому смещению в таблице виртуальных функций, но таблица то по этому указателю соответствует классу A и по такому смещению там валяется указатель на функцию A::Func1().
0
|
Deviaphan
|
23.11.2011, 12:04
#38
|
Не по теме: Преобразование базового типа к дочернему (почти) равнозначно переключению типов. А в большинстве случаев это ошибка проектирования либо интерфейса базового класса либо всей архитектуры в целом. Разумеется, в некоторых случаях это насущная необходимость, но тогда и о падении производительности речи быть не может, другого варианта всё равно нет.) В случае, если использовалось множественное наследование. Копаться в исходниках XML парсера особого желания не возникает.:)
1
|
4226 / 1795 / 211
Регистрация: 24.11.2009
Сообщений: 27,562
|
|||||||||||
23.11.2011, 12:46 | 39 | ||||||||||
Это говорит о наследовании как таковом, а множественное - это когда и указатели на два разных базовых класса могут указывать на одного и того же потомка, но не друг на друга, то есть когда одновременно:
Добавлено через 7 минут О какой дальности может идти речь при одном уровне наследования? C - это ближайший потомок из всех возможных, а здесь - единственный. Добавлено через 1 минуту Вывод говорит об обратном. Добавлено через 1 минуту Точнее классу C, но начало таблицы класса C совпадает с таблицей класса A. Добавлено через 9 минут наоборот.
1. Это указатель. 2. Ему разрешено указывать только на данные определённого типа, или только на на данные определённого класса - того типа/класса, который поминается в словосочетании. Чтоб не сбивать тебя с толку следовало бы говорить "указатель на данное типа", "указатель на объект класса", но ни кто так длинно не говорит, все сокращают и все понимают, что на саму абстракцию указатель не указывает. Добавлено через 3 минуты Не бывает объектов типа int. int не класс, а скалярный тип, переменные этого типа - не объекты. Добавлено через 3 минуты И куда ж она делась то? У всех она есть, только в твоей голове как то проделитилась. Может потому у тебя и падает всё, что с указателями, что ты не отличаешь int* от void*? Добавлено через 2 минуты Согласен. Класс - это тип, в котором разрешены любые члены, как члены-данные (поля), так и члены-функции (методы), а на некоторых языках (в том числе c++) даже члены-операторы. Но всё таки тип, а не константа. Добавлено через 6 минут Как раз неопределённо. Указатель на объект. На какой, простите, объект? На дом? На корабль? На кирпич? А может на домну? Или на пользователя? Прямо так, на настоящего пользователя в реале? А может на машинное представление данных об объекте? Эйси, всегда это подразумевается. Но о каком объекте? О Солнечной Системе? Или о пылинке? А может о стене? Пока не указан тип, не понятно о каком указателе речь, поэтому "указатель на объект" семантически идентично "указатель", прямо так, одним словом. Слово "объект" просто лишнее, оно не несёт ни какой смысловой нагрузки, так как его абстракция запредельна и не лезет ни в какие рамки, кроме как в тексте на метаязыке о самом понятии "объект" и прибамбасах для поддержки объектной ориентированности. И только когда указан класс, объект принимает хоть какие то разумные очертания, но не раньше.
0
|
Делаю внезапно и красиво
1313 / 1228 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
|
|
23.11.2011, 12:51 | 40 |
Противоречишь сам себе. Т.к.
Соответственно есть "два типа указателей" На объекты и на скаляры. А ещё есть указатели на массивы (они же на строки). Поэтому уточнение на что конкретно указывает указатель может быть не лишним. Надо же было твой пост разбить на два.
1
|
23.11.2011, 12:51 | |
23.11.2011, 12:51 | |
Помогаю со студенческими работами здесь
40
Грабли с USART_FLAG_RXNE Наступлю на те же грабли? Грабли с WM_DEVICECHANGE Грабли malloc/free Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |