LINUX.ORG.RU

Почему у меня не работает динамический полиморфизм в C++ ?

 , динамический полиморфизм,


0

2

Имею класс KnowTreeModel, унаследованный от QAbstractItemModel.

Согласно концепции динамического полиморфизма, указатель базового класса может указывать на объект производного. Однако у меня это никогда не получалось:

currentModel=knowTreeView->model();

Error: invalid conversion from 'QAbstractItemModel*' to 'KnowTreeModel*' [-fpermissive]

где:

- currentModel - имеет тип KnowTreeModel*
- knowTreeView->model() - возвращает тип QAbstractItemModel*

И я постоянно пользуюсь static_cast(), чтобы эти типы преобразовывать. Но мне говорят, что этого делать не нужно. А компилятор считает по-другому. Почему так?

★★★★★

Последнее исправление: Wizard_ (всего исправлений: 1)

Ответ на: комментарий от Xintrea

И второй вопрос: какого хрена тогда мне парили мозг, что касты не нужны в обсуждении, на которое я дал ссылку в топике:

Не читал про что там тебе парили, но касты действительно не нужны когда ты делаешь приведение типа к одному из базовых типов (upcast). При обратном действии, о котором ты спрашиваешь в этом топике (downcast), явные касты как раз обязательны. Тут весь вопрос только в том, какой каст применить. Быстрее - static_cast, но, как я говорил, ты должен точно знать, что указатель/ссылка указывает на объект конкретного типа. Ошибешься - в рантайме может произойти все, что угодно. Если же ты не можешь знать, на что тычет указатель - придется обмазываться dynamic_cast, но он работает медленнее и, если не ошибаюсь, требует наличия RTTI.

m0rph ★★★★★
()
Последнее исправление: m0rph (всего исправлений: 1)

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

Например, базовый класс Window является моделью любого окна. И имеет скажем метод Close - значит любое окно можно закрыть. Производный класс DecoratedWindow является моделью декорированного окна, и обязан иметь метод Close, так как декорированное окно является окном, но имеет ещё и метод SetTitle, задающий текст заголовка окна. Можно ли работать с классом окна как с классом декорированного окна, и задать для него заголовок? Нет, нельзя, так как обычное окно такой функциональности не имеет.

Так же и твой QAbstractItemModel не является KnowTreeModel, поэтому компилятор и сопротивляется при попытке привести тип QAbstractItemModel к KnowTreeModel.

iliyap ★★★★★
()
Ответ на: комментарий от Xintrea

Оказывается, явное приведение типа через скобки оставлено для совместимости. Обычные скобки заменила конструкция:

Плохо/мало читал. Подсказка: сишное приведение «заменяет» любой вид плюсовых кастов.

DarkEld3r ★★★★★
()
Ответ на: комментарий от i-rinat

Где ты вычитал такую глупость?

ЕМНИП, у Страуструпа так объясняется этот ужас.

Хотя с другой стороны, унификация: такой static_cast выглядит как шаблонная функция.

anonymous
()
Ответ на: комментарий от pon4ik

Если есть время - могу изложить скоуп работ и точки вмешательства.

Почитаю с удовольствием, но на этом все.

andreyu ★★★★★
()
Ответ на: комментарий от Xintrea

Тут оказывается вообще касты не нужны.
knowTreeModel=(KnowTreeModel*)( knowTreeView->model() );

Это и есть каст. В си стиле.

Неужели у вас гугель не работает. Уже сотню раз обсосали отличие си каста от с++ каста. Первая же ссылка в гугеле:
http://stackoverflow.com/questions/1609163/what-is-the-difference-between-sta...

andreyu ★★★★★
()

C++ это достаточно громоздкий язык и совсем не обязательно досконально разбираться во всех особенностях динамического полиморфизма чтобы писать на культях. Модель KISS пока еще никто не отменял.

anonymous
()
Ответ на: Оффтоп от pon4ik

Ты сделал продукт, но не в теме довольно таки основных весчей, по крайней мере судя по постам на лорчике:)

Если не секрет - сколько камрадов используют поделие?

У него хотя бы опенсорс. У меня есть страшное подозрение, что в популярных проприетарных проектах всё может быть куда хуже.

hobbit ★★★★★
()
Ответ на: комментарий от andreyu

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

Мой английский кешельме хельме до сих пор (ну точнее я как собака, всё понимаю, но ничё сказать не могу).

pon4ik ★★★★★
()
Ответ на: комментарий от hobbit

Очень спорно. Бывают проприетарные проекты с нормальным кодом и страшный опенсурс.

Зависит от подобравшейся компашки и целей.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

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

hobbit ★★★★★
()
Ответ на: комментарий от hobbit

Согласен. Но нужно учитывать что в опенсурсе часто тусуются школоло и студенты а в закрытых проектах обученные инженеры.

Поэтому я бы сказал, что в основном всё таки зависит от целей - если задача срубить бабла на коде уровня прототипа, то будет кучка испражнений, иначе - код может быть очень даже и не плох.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Но нужно учитывать что в опенсурсе часто тусуются школоло и студенты а в закрытых проектах обученные инженеры.

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

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

andreyu ★★★★★
()
Последнее исправление: andreyu (всего исправлений: 1)
Ответ на: комментарий от andreyu

Смею сообщить, что pull request с текущими возможными правками на стороне vim был вмёржен в upstream vim-rtags плагина. Можно обновиться и попробовать конфиг для neocomplete например.

Я пока пользовался - заметил ещё один косячок, не верно указывается тип комплита при omnicompletion правда.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Не осилил я настроить rtags и vim-rtags.

Установил vim-rtags, добавил такие настройки:

function! SetupNeocomleteForCppWithRtags()
    " Enable heavy omni completion.
    setlocal omnifunc=RtagsCompleteFunc

    if !exists('g:neocomplete#sources#omni#input_patterns')
        let g:neocomplete#sources#omni#input_patterns = {}
    endif
    let l:cpp_patterns='[^.[:digit:] *\t]\%(\.\|->\)\|\h\w*::'
    let g:neocomplete#sources#omni#input_patterns.cpp = l:cpp_patterns 
    set completeopt+=longest,menuone
endfunction

autocmd FileType cpp,c call SetupNeocomleteForCppWithRtags()


Установил neocomplete, конфиг:
    let g:acp_enableAtStartup = 0
    let g:neocomplete#enable_at_startup = 1
    let g:neocomplete#enable_smart_case = 1
    let g:neocomplete#sources#syntax#min_keyword_length = 3
    let g:neocomplete#lock_buffer_name_pattern = '\*ku\*'
    let g:neocomplete#sources#dictionary#dictionaries = {
                \ 'default' : '',
                \ 'vimshell' : $HOME.'/.vimshell_hist',
                \ }

    if !exists('g:neocomplete#keyword_patterns')
        let g:neocomplete#keyword_patterns = {}
    endif
    let g:neocomplete#keyword_patterns['default'] = '\h\w*'

    inoremap <expr><C-g>     neocomplete#undo_completion()
    inoremap <expr><C-l>     neocomplete#complete_common_string()

    inoremap <silent> <CR> <C-r>=<SID>my_cr_function()<CR>
    function! s:my_cr_function()
        return neocomplete#close_popup() . "\<CR>"
    endfunction
    " <TAB>: completion.
    inoremap <expr><TAB>  pumvisible() ? "\<C-n>" : "\<TAB>"
    " <C-h>, <BS>: close popup and delete backword char.
    inoremap <expr><C-h>  neocomplete#smart_close_popup()."\<C-h>"
    inoremap <expr><BS>   neocomplete#smart_close_popup()."\<C-h>"
    inoremap <expr><C-y>  neocomplete#close_popup()
    inoremap <expr><C-e>  neocomplete#cancel_popup()

    if !exists('g:neocomplete#force_omni_input_patterns')
        let g:neocomplete#force_omni_input_patterns = {}
    endif
    let g:neocomplete#force_overwrite_completefunc = 1
    let g:neocomplete#force_omni_input_patterns.c = '[^.[:digit:] *\t]\%(\.\|->\)\w*'
    let g:neocomplete#force_omni_input_patterns.cpp = '[^.[:digit:] *\t]\%(\.\|->\)\w*\|\h\w*::\w*'
    let g:neocomplete#force_omni_input_patterns.objc = '\[\h\w*\s\h\?\|\h\w*\%(\.\|->\)'
    let g:neocomplete#force_omni_input_patterns.objcpp = '\[\h\w*\s\h\?\|\h\w*\%(\.\|->\)\|\h\w*::\w*'
    let g:clang_complete_auto = 0
    let g:clang_auto_select = 0
    let g:clang_use_library = 1


Собрал проект:
$ cd build_dir && cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=1


Сообщил список команд rtags-демону:
$ rc -J .build/compile_commands.json


Вроде все работает, но как быть с проектами, которые не используют cmake?

andreyu ★★★★★
()
Последнее исправление: andreyu (всего исправлений: 1)
Ответ на: комментарий от pon4ik

Ключевые слова для поиска - <твой тулнейм> +clang +«compilation database»

На разных платформах он разный. Под линуксом в основном cmake, под макосью нативный икскодовкий.

Не радует, что нужно обновление базы при добавлении/удалении файлов в проект. В этом отношении ycm существенно проще и удобнее получается. Но он прожорлив.

andreyu ★★★★★
()
Ответ на: комментарий от andreyu

Не радует, что нужно обновление базы при добавлении/удалении файлов в проект

Ну, cmake сделает это автоматом, а так, да не очень удобно, но по факту это то, что и делают все ide с clang бэкэндом.

Можно написать простенький демонок аля:

while:
do
    find \( -name '*.cpp' -or '*.h' -or '*.hpp' \) > list.cur;
    diff list.cur list.back || <regenerate compilation database>;
    cp list.cur list.back;
    sleep 3;
done

По хорошему надо этот функционал передавать на сторону rtags, но тут у меня дизайна пока что даже в голове нет как надо.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

По хорошему надо этот функционал передавать на сторону rtags,

Лучше на сторону rdm. Или вы как раз о нем и говорите?

но тут у меня дизайна пока что даже в голове нет как надо.

Про дизайн я хз, а идея мне видится такой - rdm получает на вход конфиг, в котором указан рут проекта и параметры компиляции, после чего мониторит изменения в руте на предмет создания/удаления файлов.

andreyu ★★★★★
()
KnowTreeModel *km = new KnowTreeModel;
QAbstractItemModel *m;
m  = km; // так можно
km = m;  // так без преобразования нельзя.
robus ★★★★★
()
Ответ на: комментарий от andreyu

rdm - часть rtags, так что да, о нём.

Про дизайн я хз, а идея мне видится такой - rdm получает на вход конфиг, в котором указан рут проекта и параметры компиляции, после чего мониторит изменения в руте на предмет создания/удаления файлов.

Плохая идея - как определить флаги компиляции для новых файлов?

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Плохая идея - как определить флаги компиляции для новых файлов?

Так же как и для старых - конфиг + маска + мониторинг изменения конфига.

andreyu ★★★★★
()
Ответ на: комментарий от andreyu

А если в проекте несколько целей, с разными флагами?

Например две либы подсистем, с разными инклудопутями?

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

А если в проекте несколько целей, с разными флагами?
Например две либы подсистем, с разными инклудопутями?

Ну так точно так же - переключаем, если есть такая необходимость. Сейчас это как реализовано?

andreyu ★★★★★
()
Ответ на: комментарий от andreyu

Сейчас есть единый compilation database. Там просто перечисленны все файлики (или маска) и соответсвующие им флаги компиляции.

Clang ничего не знает и знать не должен про цели :)

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Сейчас есть единый compilation database. Там просто перечисленны все файлики (или маска) и соответсвующие им флаги компиляции.

rtags умеет переключать проекты. По идее так же можно переключать и цели.

Clang ничего не знает и знать не должен про цели :)

Ну так я с этим не спорил, это вы спросили что в таком случае нужно делать.

andreyu ★★★★★
()
Ответ на: комментарий от robus

В общем сделал себе такую записульку:

http://webhamster.ru/mytetrashare/index/mtb0/1459542913jsssnlsm5t

Видимо, пока я не напишу собственный компилятор, я не пойму, почему такая проблема присваивать указатели. Указатель, независимо от типа данных - это просто адрес. Зачем так говняшиться, в одну сторону - да, в другую - нет, мне соврешенно не понятно.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Зачем так говняшиться, в одну сторону - да, в другую - нет, мне соврешенно не понятно.

Надеюсь, что ты троллишь...

DarkEld3r ★★★★★
()
Ответ на: комментарий от pon4ik

Проект в терминах rtags это один compilation database.

Я на странице vim-rtags видел такой хоткей - <Leader>rl
У меня он предлагал выбрать проект. Почему же его нельзя использовать для переключения между проектами и таргетами?

andreyu ★★★★★
()
Ответ на: комментарий от Xintrea

Указатель, независимо от типа данных - это просто адрес. Зачем так говняшиться, в одну сторону - да, в другую - нет, мне соврешенно не понятно.

Что бы уменьшить возможность выстрела в ногу. Посмотрите, как устроена vtbl.

andreyu ★★★★★
()
Ответ на: комментарий от Xintrea

Я не понимаю такую политику контроля типов.

В этой теме было много аргументов и примеров. Было бы хорошо, если ты написал с чем конкретно не согласен или что именно вызывает непонимание. Давай попробуем ещё раз.

Вот есть у тебя базовый тип Base и пишешь ты библиотеку, то есть всех его наследников, как обычно и бывает, знать не можешь:

struct Base {
    virtual void foo() { ... }
}

void do_something(const Base& object) {
    object.foo();
}
Очевидно, что вызвать функцию foo для всех наследников мы можем вполне безопасно, просто из-за самой природы наследования. Поэтому приводить к базовому классу безопасно, потому для каста и не требуется дополнительных телодвижений.

Дальше твою библиотеку начинают использовать и создают 100500 наследников от Base. Какой реальный тип будет скрываться за указателем/ссылок на Base ни ты, как автор библиотеки, ни компилятор знать, в общем случае, не могут.

struct A : Base {}
struct B : Base {}
И если попытаться привести указатель на Base к указателю на A, в то время как на самом деле по указателю будет лежать B, то ничего хорошего из этого не выйдет. Вот компилятор и подстраховывает тебя, требуя явного указания, что ты знаешь что делаешь.

Опять же, тут могут быть разные стратегии: если есть 100% уверенность, что там будет нужный тип, то можем использовать static_cast. Если уверенности нет и/или хочется делать разные действия в зависимости от типа - dynamic_cast. Более того, как в теме уже подсказывали, в Qt есть свой qobject_cast, который для наследников Object работает эффективнее, чем dynamic_cast. Но компилятор о нём не знает - это уже твоя зона ответственности.

Неужели это не понятно?

DarkEld3r ★★★★★
()
Ответ на: комментарий от andreyu

Посмотрите, как устроена vtbl.

Боюсь, что такой совет не по адресу.

DarkEld3r ★★★★★
()
Ответ на: комментарий от andreyu

Ибо

Проект в терминах rtags это один compilation database

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

pon4ik ★★★★★
()
Ответ на: комментарий от Xintrea

Зачем так говняшиться, в одну сторону - да, в другую - нет, мне соврешенно не понятно.

// Downcast
child = base;  // так без преобразования нельзя (base не имеет всех данных для child)

base не имеет всех данных для child

robus ★★★★★
()
Ответ на: комментарий от pon4ik

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

А для чего тогда нужен выбор проекта? Ведь какая-то цель была у разработчиков плагина.

andreyu ★★★★★
()
Ответ на: комментарий от andreyu

Очевидно же - разделить на проекты:)

Вот у меня например rtags(сабж), rtags-test-project(песочница), ну и пара моих поделий.

При этом, в rtags есть как минимум 3 верхнеуровневых цели: rdm, rc, librct...

Но шлангу глубоко начхать на то что получается из единиц трансляции, его задача корректно, одну за одной их скомпилировать и занести в свой индекс для прочих манипуляций.

Нужен менеджер проекта - это его зона ответсвенности, следить за файлами, целями и прочими сопутсвующими вещами.

В rtags просто не очень удачна названна абстракция.

У меня была масштабная задумка как порешать сию проблему - но я не готов, пока, потратить столько времени на opensource.

Даже была предпринята попытка начать (чужими руками - тип кодил а я ревьювил, иногда что то пояснял), но я пришёл к тому, что начинать надо с разработки протокола и чёткого дизайна что есть проект.

pon4ik ★★★★★
()
Ответ на: комментарий от andreyu

Кстати - чувак который кодил мою задумку, загорелся продолжить сиё. Можешь смело подхватить инициативу, если интересно, я вас сконтачу.

pon4ik ★★★★★
()

как же у меня от тебя бомбит.

компилятор умеет выполнять только восходящее преобразование типов: от потомка к родителю: QAbstractItemModel* model = new KnowTreeModel();

компилятор не умеет преобразовывать от родителя к потомку. полиморфизм работает в рантайме для виртуальных методов базового класса. тебе не нужно приводить тип к KnowTreeModel* чтобы вызвать виртуальный метод. ты его на QAbstractItemModel* вызывай. тебе нужно приводить тип к KnowTreeModel* только если у QAbstractItemModel нет нужного метода. но в таком случае вся иерархия классов - говно.

x0r ★★★★★
()
Ответ на: комментарий от DarkEld3r

За развернутый ответ спасибо. Это все понятно.

Непонятно другое, то, что написали по ссылке в топике. То есть, мы конструируем программу на C++, и вынуждены при даункасте прописывать приводимый тип. Предположим, структура программы меняется, добавляется например еще один слой абстракции, и он у нас самый верхний, с которым мы взаимодействуем. Получается, что нам нужно находить все места в программе, и прописывать даункаст на новый тип.

Это никак не согласуется с заявлением: «Вот захочешь завтра добавить пару-тройку очередных SuperNewDerived с какой-то особой, расширенной логикой - и поймешь зачем».

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от x0r

тебе не нужно приводить тип к KnowTreeModel* чтобы вызвать виртуальный метод. ты его на QAbstractItemModel* вызывай.

Имеешь в виду, что переопределенный в KnowTreeModel виртуальный метод правильно вызывать не у объекта типа KnowTreeModel, а у объекта типа QAbstractItemModel?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Это никак не согласуется с заявлением: «Вот захочешь завтра добавить пару-тройку очередных SuperNewDerived с какой-то особой, расширенной логикой - и поймешь зачем».

Мне лень восстанавливать контекст из другой темы, чтобы оценить корректность утверждения, поэтому попробую зайти с другой стороны. А именно поддержу мнение, что в большинстве случаев, кастов скорее сигнализируют о кривости дизайна.

Если нужные интерфейсы (виртуальные функции) имеются в базовом классе, то нам всё равно сколько SuperNewDerived добавится - при вызове функции по указателю/ссылке на базовый класс всё равно вызовется реализация конкретного наследника.

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

DarkEld3r ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.