Базовый курс C++ (MIPT, ILab). Lecture 8. Наследование и полиморфизм

Поділитися
Вставка
  • Опубліковано 10 лис 2024

КОМЕНТАРІ • 68

  • @std-sort
    @std-sort 4 місяці тому +5

    Не встречал на русском более качественного курса чем у Вас. Большое спасибо за труд :-)

  • @Dima-Teplov
    @Dima-Teplov Місяць тому +1

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

  • @kotanvich
    @kotanvich Рік тому +11

    Какие же крутые лекции! Но как же хотелось бы увидеть лекции по си в вашем исполнении!

  • @samolisov
    @samolisov 3 роки тому +9

    1:34:41 Паттерн "Public Морозов"

  • @evgenyrozhnowsky6572
    @evgenyrozhnowsky6572 2 роки тому +1

    Спасибо за лекции! Безумно интересно)
    Есть опечатка в слайде №54 (1:35:08 ) строки struct Derived : public Base, необходимо : public BaseNVI

  • @1968ussr
    @1968ussr 8 місяців тому

    Приятные воспоминания. Второй курс, только не компилятор, а интерпретатор, мощного и лаконичного подмножества русского языка. Жаль только на кафедре к этому отнеслись немного странно. 😉

  • @ufabiz
    @ufabiz 3 роки тому +1

    Спасибо за очередную замечательную лекцию!
    По поводу замечания в 55:41 писать или не писать virtual. Это от части code-style. Но у привычки писать virtual есть одно важное преимущество:
    Когда читаешь код, virtual у overrided функций помогают лучше видеть интерфейс класса.
    virtual всегда идёт перед функцией, его проще увидеть.
    Разумеется, это замечание не относится к ревью, а только к правильно написанному коду.

    • @tilir
      @tilir  3 роки тому +2

      Мне кажется override это довольно длинная и заметная аннотация...

    • @bochkarevartem
      @bochkarevartem 3 роки тому

      @@tilir кстати, почему override решили сделать после функции? Казалось бы, лучше было просто перед функцией вместо virtual ставить override...

    • @tilir
      @tilir  3 роки тому +1

      @@bochkarevartem большинство аннотаций после. Мне скорее удивительно что virtual до.

    • @bochkarevartem
      @bochkarevartem 3 роки тому

      @@tilir раз virtual был до, то и override логично было бы сделать до. Единственное, что override как расширение разных компиляторов существовал до включения в стандарт и как раз везде шел после. Ну и кстати, аттрибуты появились в стандарте одновременно с override, и идут перед. Вполне могло быть [[override]], но, видимо, решили сделать как уже устоялось. Просто вдруг были какие-то особые причины, о которых я даже не задумывался..

    • @tilir
      @tilir  3 роки тому

      @@bochkarevartem мне таких причин неизвестно. Но с другой стороны мне и глаза особо не режет, что он после.
      Я не думаю что это мог бы быть атрибут. Всё-таки override это что-то более фундаментально. Атрибуты обычно касаются только самой функции. Override контролирует свойство семейства классов в которых входит динамически полиморфная функция.
      Мне вот ещё непонятно почему нельзя было запилить это как ключевое слово...

  • @ivankorotkov2563
    @ivankorotkov2563 3 роки тому +9

    Спасибо!
    Вопрос по поводу слайда 24 (48:25).
    Насколько мне известно работа с vptr все-таки обычно организована несколько иначе. На слайде приведена упрощенная картина для более простого понимания, или есть какие-то специфические имплементации, устроенные таким образом?
    Насколько мне известно в момент компиляции программы уже известны какие виртуальные функции должна содержать таблица виртуальных функций для каждого класса, поэтому перед выполнением программы все таблицы виртуальных функций уже содержатся в статических данных, а все что делают конструкторы-деструкторы, это просто переставляют указатель vptr конкретных экземпляров классов на эти таблицы. И судя по листингу ассемблера эта таблица не обязана заканчиваться nullptr.
    Я проверил по нескольким компиляторам и там нигде нет динамической аллокации, а работает именно как я описал.
    Если предположить что конструктор должен динамически выделять память под vtable, то в голову приходит как минимум пара проблем:
    1) должны ли разные экземпляры одного класса разделять одну таблицу, или у каждого экземпляра должен быть свой экземпляр? Если каждый инстанс хранит свою копию таблицы, то это выглядит супер дорого, если нет, то кто отвечает за удаление таблицы.
    2) поскольку производные классы могут накидывать дополнительные поля в таблицу виртуальных функций, то базовый класс на момент создания не может знать размер таблицы виртуальных функций. И либо производные классы должны ее переаллоцировать, либо в базовый конструктор нужно еще прокидывать неявный параметр на реальный размер таблицы для этого класса, ну или еще какое-нибудь костыльное решение. Все это выглядит не очень, на фоне просто метода, описанного выше.

    • @tilir
      @tilir  3 роки тому +3

      Разумеется это очень упрощенная картинка. Спасибо за развернутый комментарий )) Книжка Липмана по деталям объектной модели будет у меня в списке литературы.

  • @sehzadeselim863
    @sehzadeselim863 Рік тому +2

    На задании 1:50:30 может, можно проинициализировать IBuffer нашим MyBuffer? 😅Или это вызовет проблемы, которые я не вижу?(

  • @Vakenrovec
    @Vakenrovec 2 роки тому

    1:00:40
    В строке ISquare *sq = new Triangle; delete sq;
    Откуда operator delete знает сколько байт нужно освободить? Почему он освободит sizeof(Triangle), а не sizeof(ISquare)?

  • @samolisov
    @samolisov 3 роки тому +2

    Кстати, вспомнил, как можно вызвать деструктор наследника, если есть ссылка на базовый класс и у того деструктор невиртуальный. Есть же правило, что константные ссылки продляют время жизни временных объектов и такой код const Basic &b = make_derived() вполне валиден если make,_derived() возвращает объект класса Derived по значению. Тогда при окончании времени жизни b будет вызван деструктор временного объекта, Derived, а не Basic, несмотря на тип ссылки. И здесь неважно виртуальный деструктор у Basic или нет.

  • @ДмитрийСадков-е8ц
    @ДмитрийСадков-е8ц 2 роки тому +2

    Константин, похоже на слайде 43 опечатка:
    нужно заменить
    unique_ptr(T *ptr = nullptr, Deleter del = Deleter()) :
    Deleter(del), ptr_(ptr), del_(del) {}
    на это
    unique_ptr(T *ptr = nullptr, Deleter del = Deleter()) :
    Deleter(del), ptr_(ptr) {}

    • @tilir
      @tilir  2 роки тому

      Да вы правы. Самое смешное, что мне понадобилось вглядываться в то что вы написали чтобы осознать отличие =) Спасибо, забыл убрать при эволюциях кода.

    • @ДмитрийСадков-е8ц
      @ДмитрийСадков-е8ц 2 роки тому +1

      @@tilir таже самая опечатка есть на слайде 64.

    • @ДмитрийСадков-е8ц
      @ДмитрийСадков-е8ц 2 роки тому +1

      еще микро опечатка на слайде 57:
      ...эффективно воВзводить разреженные матрицы...

  • @Indev29
    @Indev29 2 роки тому

    Спасибо за лекции!
    1:29:25 Насколько я понимаю, early/late binding - это про связывание имени с конкретным типом, в C++ же имена всегда(?) связываются статически.
    Терминология довольно расплывчатая и сбивает с толку. Вплоть до того, что статьи Late Binding и Dynamic Dispatch на вики напрямую друг другу противоречат в разделе о C++.
    Есть точка зрения, что late binding в C++ не поддерживается и называть dynamic dispatch (которым является вызов виртуальных функций и наверное любой type erasure в языке) late binding'ом некорректно.
    При этом постоянно вижу, что late binding и dynamic dispatch используются как взаимозаменяемые понятия, а потому не могу уложить их в голове, ведь они означают совершенно разные вещи.
    Можете что-то сказать по этому поводу?

    • @tilir
      @tilir  2 роки тому

      У нас есть связывание на уровне языка: оно бывает статическим или динамическим (через виртуальные функции). И у нас есть слой под языком, когда статическое (на уровне языка) связывание может реализоваться через механизмы разделяемых библиотек и т.п. Это ортогональные вещи. На этих лекциях я под статическим и динамическим связыванием имею в виду только связывание на уровне языка, независимо от того какие реальные механизмы связывания работают там, под водой.

  • @Юлий-ы5г
    @Юлий-ы5г 2 роки тому +1

    Как же хорошо, что в С++ union без конструктора это ошибка компиляции. Ведь могли бы выбирать конструктор поля основываясь, например, на прогнозе погоды...

  • @oltimuss
    @oltimuss 3 роки тому

    На слайдах 13 и 14 (27:14) видимо произошла путаница для определений double_square. На 13 принимает Square &s, но не использует; а на 14 нет аргументов, но использует s в теле метода.
    P.S. Огромное спасибо за познавательные лекции!

    • @tilir
      @tilir  3 роки тому +1

      Спасибо за внимательность. Ошибка на 13-м слайде, там должен быть просто метод.

  • @ДмитрийАндреев-ф3х
    @ДмитрийАндреев-ф3х 7 місяців тому

    Имеет ли смысл внутри дочернего класса вызвать using родительского?
    как на 1:43:23 using Matrix; вместо using Matrix::pow;

  • @stanislavstanislavius7618
    @stanislavstanislavius7618 3 місяці тому

    Контест по си накрылся( ну, хоть успел все прорешать, кроме пары первых заданий из первого семинара

  • @goczt
    @goczt 3 роки тому +4

    51:24 Опечатка "SparceMatrix" - должно быть "SparseMatrix". Отследить что-то кроме орфографических ошибок компетенции не хватает

    • @tilir
      @tilir  3 роки тому

      Тоже неплохо, спасибо, поправлю на слайдах.

  • @ddvamp
    @ddvamp 2 роки тому +1

    Добрый вечер. Почему бы на 9:52 просто не использовать mem-initializer-list вместо placement new?
    union U {
    std::string s_;
    std::vector v_;
    U(std::string s) : s_(std::move(s)) {}
    U(std::vector v) : v_(std::move(v)) {}
    ~U() {}
    };

    • @tilir
      @tilir  2 роки тому +2

      Не знал что так можно. Это несколько реабилитирует union как технику в C++.
      Оставлю для памяти: godbolt.org/z/f5cTe9v55

  • @samolisov
    @samolisov 3 роки тому +1

    User-provided vs user-declared, чувствуешь себя немного юристом, когда такое читаешь. "Проценты за месяц начисляются по минимальной сумме, которая была на счёте в этот месяц, если хоть в один день на счёте был ноль, то проценты не начисляются." - А если я открыл счёт второго числа, то первого на нем был ноль? - Нет, первого числа счёт не существовал, а значит на нем не был ноль. Вот какие-то такие аналогии на ум приходят, когда стандарт читаешь.

    • @tilir
      @tilir  3 роки тому +2

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

  • @razenkovv
    @razenkovv 8 місяців тому

    Здравствуйте, не очень понял на 1:50:00. Порядок инициализации объектов же не зависит от их порядка в списке инициализации? То есть Array будет проинициализирован первым не зависимо от того на каком месте он будет в списке инициализации. И он будет проиницилизирован указателем на непроинициализированную область памяти, и соответственно потом это останется висячим указателем. Но такой код скомпилируется. (Рассуждения для себя, если я ошибаюсь, буду рад если кто нибудь поправит)) Автору канала огромное спасибо за такие лекции!

    • @tilir
      @tilir  8 місяців тому

      Нет, порядок инициализации подобъектов не зависит от их порядка в списке инициализации, а только от порядка внутри определения класса.

    • @razenkovv
      @razenkovv 8 місяців тому

      @@tilir Ага, понял, спасибо

  • @The604FX
    @The604FX Рік тому +1

    Здравствуйте.
    Не совсем понимаю, как функции "хранятся" (понимаю, что не хранятся они там) внутри класса? То есть, по факту, это функции, которые принимают неявно this, но как это реализовано на практике?
    При использовании пространств имён механизм схожий?

    • @tilir
      @tilir  Рік тому +2

      Функция не "хранится внутри класса". Функция-член это просто функция с манглированным именем принимающая this первым неявным аргументом. Пространство имён это плюс к манглированию имени. Я собственно показываю как это устроено в первых лекциях курса. Возможно вы слишком забежали вперёд.

  • @viacheslavbarkov6538
    @viacheslavbarkov6538 3 роки тому

    1:35:21, слайд 53, наверно имелось в виду struct Derived : BaseNVI
    Ну и можно было сделать foo_impl protected в обоих классах

    • @tilir
      @tilir  3 роки тому

      Да действительно, досадная опечатка. А зачем protected?

    • @viacheslavbarkov6538
      @viacheslavbarkov6538 3 роки тому

      @@tilir это уже больше к красоте. Изменение уровня доступа при переопределении на мой взгляд не очень хорошая практика. Например в языках Java и C# она не разрешена, как и приватные виртуальные методы.
      Знаю людей, которые при реализации интерфейса, делают все методы приватными, чтобы можно было только через интерфейс вызывать

  • @bkWorm-gx2pi
    @bkWorm-gx2pi 6 місяців тому

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

  • @qp7059
    @qp7059 Рік тому

    Если создаем абстрактный класс, компилятор выдает варнинг: виртуальная таблица будет создана в каждой единице трансляции. Добавление какой-либо функции с телом в .cpp решает эту проблему. Есть ли другие способы решения этой проблемы?

  • @xsomniator
    @xsomniator 2 роки тому

    Можем ли мы добавить в список примеров полиморфизма (перегрузка, шаблоны, виртуальные методы) класс variant с функцией visitor? Это полиморфизм в рантайме по типу хранимого значения, начиная с C++17 является частью стандарта.

    • @tilir
      @tilir  2 роки тому

      std::visit это довольно обычный compile-time полиморфизм. Там нет стирания типов и нет необходимости ничего решать в рантайме.

  • @wildboar5565
    @wildboar5565 9 місяців тому

    Спасибо за лекции! Правда я не до конца понял зачем нужны пустые классы и зачем EBCO и в каких случаях это используется🙁

    • @wildboar5565
      @wildboar5565 9 місяців тому +1

      Стоило мне попробовать поиграть с этим - вопрос разрешился сам собой. Гениальный эксплоит

    • @poigrushkin9433
      @poigrushkin9433 9 місяців тому

      ​@@wildboar5565харош, жги

  • @d7d1cd
    @d7d1cd 3 роки тому

    Слайд 63.
    Хотелось бы поумничать ). Если Part - пустой класс, то при композиции размер Whole будет больше, чем при наследовании.

    • @tilir
      @tilir  3 роки тому

      EBCO тоже разбирается на этой лекции. То есть если он пустой все знают что делать.

  • @Ii_Naomasa_san
    @Ii_Naomasa_san 3 роки тому +1

    Лекция супер! Но мне показалось, что не раскрыт вопрос: а когда использовать статический, а когда динамический полиморфизм? В каких случаях, что применять?

    • @tilir
      @tilir  3 роки тому +1

      Лекция про проектирование будет отдельно. Сейчас мы просто проходим средства языка. Кое-что о сценариях применения конечно говорится, но паззл пока не собран.

    • @Ii_Naomasa_san
      @Ii_Naomasa_san 3 роки тому

      @@tilir Ясно. Понятно). Может быть подскажите литературу, где можно найти ответ на этот вопрос. Был бы очень благодарен :)

    • @tilir
      @tilir  3 роки тому +2

      @@Ii_Naomasa_san мне из последних понравилась книга Пикуса "Hands-On Design Patterns with C++" (есть в русском переводе, издана в этом году). Всё очень по делу.

    • @SeriousMan212
      @SeriousMan212 7 місяців тому

      ​@@tilir О Пикус. Крутой мужик. Несколько раз бывал у него на публичных выступлениях. У него всегда с собой заготовочки нескольких строк кода и бенчей, через которые открываются двери в "Нарнию" =)

  • @alexskatkov2124
    @alexskatkov2124 2 роки тому

    Добрый день!
    Когда станет открытым курс по компиляторостроению? Только из-за вас заинтересовался этой темой. В хорошем смысле, конечно! Спасибо! Читаю "умные книжки", но мне этого не хватает. Хочу когда-нибудь увидеть ваши лекции и задания по курсу. Может быть, можно где-то посмотреть задание для компилятора ParaCL? Что-то вроде методы?

    • @tilir
      @tilir  2 роки тому

      Курс по компиляторам пока не сделан. Очень надеюсь что в ближайшие годы я к нему хотя бы приступлю. Мне есть что сказать на эту тему. Пока что всё, что я читаю, это несколько небольших лекций: ParaCL, clang, LLVM, toolchain, вот это всё.

    • @alexskatkov2124
      @alexskatkov2124 2 роки тому

      @@tilir То есть ребята пишут интерпретатор ParaCL со знаниями спецкурса по тулчейнам и курса бакалавров, где некоторые лекции поднимают в какой-то форме вопросы по парсингу, автоматам и всему такому?

    • @tilir
      @tilir  2 роки тому

      Нет, они пишут интерпретатор со знаниями в объёме iLab второго курса, который я сейчас и выкладываю.

  • @user-b73mpf5uq3
    @user-b73mpf5uq3 2 роки тому

    19:45 Есть такой простенький вопрос.
    Насколько корректно в качестве напоминания, писать вот так:
    struct BinOp
    :
    public Node
    {
    /////
    };
    Идея в том, чтобы подчеркнуть сказанное (это как бы поле, которое лежит над структурой производного класса).
    Конкретно, вопрос в том - какова удобочитаемость?
    Не сильно ужасно, если я придерживаюсь такого стиля в некоторых местах?

    • @tilir
      @tilir  2 роки тому

      Мне кажется, что наследование в строчку это классика и по умолчанию читатель кода будет искать базовые классы справа по коду, а не снизу. Это сложившаяся норма, ломать её -- себе дороже.
      Я лично эти проблемы решаю так. Студенты пишут как угодно, а я, когда ко мне приходят их проекты, прогоняю их через индентацию (например clang-format) с дефолтными настройками. Убирая цветущую сложность и уникальные идеи по форматированию, но -- только на своей стороне, не вмешиваясь в творческий процесс и не выламывая руки.
      Так и вы. Выставите под ваш стиль настройки индента и редактируйте в таком стиле у себя, если вам так удобнее, никто и слова не скажет. Собственно никто и не узнает =)

    • @user-b73mpf5uq3
      @user-b73mpf5uq3 2 роки тому +1

      @@tilir хорошо, спасибо.
      И спасибо за хорошие лекции! Очень вкусная пища для мозга)

  • @ДмитрийАндреев-ф3х
    @ДмитрийАндреев-ф3х 8 місяців тому

    это сложно

  • @MVZ1983
    @MVZ1983 6 місяців тому

    да... хак на хаке и хаком погоняет
    хороший курс лекций, но как с этим жить?

  • @vuniverserb1142
    @vuniverserb1142 2 роки тому +1

    Доброго времени суток. "Агрегатная иницилизация работает если все поля в одной секции" на ~21 минуте. godbolt - z/h5sGqYcM4 У меня при приватной части ошибку выдает, хотя от туда от куда инициализирую права доступа есть. Заранее спасибо!

    • @tilir
      @tilir  2 роки тому

      Это меня что-то проглючило. По [dcl.init.aggr] An aggregate is an array or a class with [...] no private or protected direct non-static data members кроме всего прочего. Но я даже не могу внести это в errata так как лекция не про это и того нет на слайдах. Тем не менее спасибо за внимательность.

    • @vuniverserb1142
      @vuniverserb1142 2 роки тому

      @@tilir Спасибо