Базовый курс C++ (MIPT, ILab). Lecture 13. Проектирование

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

КОМЕНТАРІ • 70

  • @ufabiz
    @ufabiz 2 роки тому +37

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

  • @randomcraft2345
    @randomcraft2345 Місяць тому

    Ох. Одновременно крайне рад за ваших студентов и сочувствую им. Преподаватель им попался просто замечательный, правда часто рассматривает так подробно, что нужно уже не просто изучать язык, а хоть немного потыкаться в него, т.к. нужно уже хорошо плавать в нём, чтобы следить за вашим ходом мысли без лишних раздумий (тем студентам, которые не делали ни одной HWT к моменту этой лекции- соболезную). Но, что не убивает - делает сильнее! В крайнем случае, потом смогут посмотреть повтор, за что отдельное вам огромное спасибо, но уже от меня, т.к. могу прикоснуться к такому обучающему материалу, не будучи студентом МФТИ. Впрочем, думаю в МФТИ не очень много студентов, которые просто слушают лекции и в свободное время ничего не практикуют.
    Просто огромаднейшее спасибо вам! Крайне рад слушать эти лекции, и судя по комментариям не я один! Не знаю насколько большой ваш вклад профессиональный, но в преподавании - 100% точно большой! Обычно говорят, что если из тебя не получается хорошего специалиста - иди учить; даже учитывая, что мало знаю вас, этот "совет" явно к вам не относится!
    В прицнипе, обычно не смотрю на других, только сам на себя, но на вас прям так и хочется ровняться. Особенно очень ценно, что несмотря на вашу явную профессиональность - видно, что всё равно знаете куда расти, к чему стремиться! Это тоже восхищает!
    А так же непомерно благодарен отсальным, кто сделал возможным просмотр этих лекций; кто разрешил их выкладывать на UA-cam, кто помогал с лекциями, и т.п..

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

    Здравствуйте, Константин. Большое спасибо за отличную лекцию. Хочу указать на небольшую, на мой взгляд, неточность. На 18 слайде (35:43) от IScreen к IFigure на слайде проведено отношение агрегирования. Однако интерфейс не может выступать в роли «Целого», так не имеет полей-данных. Агрегировать может только класс. Здесь корректнее было бы провести отношение зависимости IScreen от IFigure (пунктирная стрелка)

  • @antonelphimov2062
    @antonelphimov2062 2 роки тому +5

    Спасибо огромное за лекцию. Подскажите, на ютубе есть ваша лекция по паттернам проектирования?

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

      Пока нет, но рано или поздно...

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

    Спасибо за лекцию очень интересно. По паттернам по моему скромному мнению лучше прочитать Эрика Фримена.

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

      Эта книга мне всегда казалась немножко слишком заточенной на джаву. И не слишком серьёзной.

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

    Большое спасибо за интересную и полезную лекцию . У меня вопрос - планируются ли ещё лекции в плане проектирования ПО , а конкретно объединение классов в пакеты , критерии «хорошего» пакета и зависимости между пакетами . Спасибо

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

      Спасибо. То что вы предлагаете это не базовый уровень, т.к. понятие пакета в языке не определено. Это что то вроде допсеминара для магистрантов. Очень специфичного. Я подумаю, конечно, но я не уверен, что я сам тут компетентен.

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

      Про физический дизайн хорошо пишет и рассказывает Лакос, Large-Scale C++ Software Design (интересно, пропустит ли Гугл коммент в этот раз).

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

    В примере DIP нет инверсии зависимости как таковой, т.е. направление зависимостей в примерах осталось неизменным.
    Если у нас есть некий модуль, реализующий "высокоуровневую логику" и есть "низкоуровневая система ввода-вывода", то система ввода-вывода предоставляет интерфейс для выполнения этого самого ввода-вывода. И это еще одна паразитная зависимость - высокоуровневая логика зависит от интерфейса системы ввода-вывода, т.е. зависимость направлена от высокого уровня к низкому. А вот если в нашем модуле логики будет интерфейс ввода-вывода, а система ввода-вывода будет реализовывать данный интерфейс, то зависимость будет идти от низкого уровня к высокому, т.е. мы инвертировали её, тем самым модуль логики стал более самодостаточен. Если не ошибаюсь, подобный пример и в книге Мартина приводится.

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

      В моём примере всё обстоит именно так. Планировщик зависел от конкретной реализации базового блока. Мы сделали интерфейс и теперь базовый блок реализует этот интерфейс. Вроде один в один. Я что-то упускаю?

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

      @@tilir наверное, я не очень понятно изложил что имею ввиду (всё-таки Вы прекрасный лектор, а я среднестатистический "эксперт"). Набросал диаграмму, которая, надеюсь, решит наше недопонимание, но злой youtube не пускает ссылку в комментарии, поэтому только так: diskТОЧКАyandexТОЧКАru/i/bD3vfr3yqoV_8Q

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

      Спасибо, это отличная трактовка Мартина. Продублирую ссылку (надеюсь меня не удалит): disk.yandex.ru/i/bD3vfr3yqoV_8Q
      Отношения те же самые, но другая физическая компоновка модулей. У меня в лекции не было про физическую компоновку, только про отношения между классами. Но методически это прекрасное объяснение почему dependency inversion это именно inversion. Я, пожалуй, скорректирую своё изложение в следующем году с учётом этого.

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

      На самом деле в примере в лекции наверное не хватает какой-то пунктирной линии, отделяющей один модуль от другого, с ней все станет на свои места. Scheduler и IDAG это один модуль, DAG и IBB - другой, а конкретные BB - третий. Scheduler говорит, мол умею работать с DAG, который реализует IDAG, Конкретный DAG говорит, что он работает с IBB, нижележащий слой должен реализовать, т.е. зависеть от этого IBB.
      В реальном LLVM используется похожий подход для реализации CFG, только не через интерфейсы, а через специализации шаблонов (чтобы построить CFG из сепулек надо специализировать некий шаблон для сепульки). Впрочем было предложение и бурно обсуждалось переписать на ООП с динамическим полиморфизмом, не помню уже аргументацию, но проблема в том, что слишком много CFG в проекте: для IR - один, для MachineIR - другой, а ещё MLIR есть...

    • @НиколайИванов-у8т
      @НиколайИванов-у8т 2 роки тому

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

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

    Множество интерфейсов это наш Java путь. В С++ шаблоны позволяют реализовать duck typing и обойтись без общих интерфейсов. Хотим реализовать поиск по массиву или коллекции - не нужно никаких ": public Iterable", достаточно чтобы в коллекции были объявлены два метода: begin() и end(). Выигрыш - производительность, т.к. нет виртуальных вызовов. Увы за это приходится платить дикими, зачастую нечитабельными сообщениями об ошибках компиляции либо обмазыванием кода enable_if/static_assert, либо (с С++20) дисциплиной в прописывании концептов. Ну и худшей поддержкой в различных IDE, т.к. пока шаблон не инстанцировался, то непонятно есть нужные методы у его параметров или нет.

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

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

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

      Про Iterable, я имел ввиду как оно было бы без шаблонов и как сейчас есть в Java и других языках, хотя тот же Rust позволяет вызывать методы класса (структуры) - параметра без виртуальных вызовов за счёт мономорфизации, но и виртуальная диспетчеризация никуда не делась, можно пользоваться при желании. При этом компилятор проверит, что переданный класс реализует требуемый интерфейс (трейт) Т и обойти эту проверку никак нельзя. По поводу коробочек с игрушками, есть идея заводить по коробочке на каждый тип игрушки, тогда по ним можно будет итерироваться и что-то делать без виртуальных вызовов и наши кеши останутся мягкими, шелковистыми и прогретыми. Да впрочем понимаю, что вы и сами это все знаете, эта лекция про классический ООП, но классическому ООП постоянно кто-то наступает на пятки.

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

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

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

      @@tilir Девиртуализация и инлайнинг выглядят как оптимизации, могут произойти, а могут, если нет данных о конкретном типе, - не произойти. Я наверное заоффтопил, но привел мономорфизацию как один из механизмов реализации шаблонов/дженериков. В Java, например, все вызовы виртуальные поэтому в общем случае можем просто очистить тип нашего параметра Т после компиляции (по историческим причинам так и сделали). В Rust мы поступаем похожим на С++ способом (как я это понимаю): компилируем шаблонный метод для каждого Т отдельно в какой-нибудь foo и отдельно foo. И там компилятор требует если в шаблоне используешь какой-нибудь вызов T.method(), то будь добр укажи, что T реализует какой-нибудь интерфейс, содержащий метод method(), без этого не скомпилирует, отвалится на этапе семантического анализа. А в С++ мы просто натурально заменяем Т на нужный тип на этапе переписывания AST (как в Rust не знаю, по моему мономорфизация производится позже уже на этапе обработки или генерации MIR). Так как мы просто переписываем AST, то нам достаточно просто указать template , никаких ограничений, что такое T указывать не нужно, есть там нужные поля или методы - гуд, нет - получи, программист, ошибку и это твоя забота получить ее как можно раньше. Мне подход Rust нравится больше, хотя плюсовый гибче, но никак не покидает мысль, что пишешь как на Python каком-нибудь.

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

      @@tilir а вообще вы правы, от языка здесь только то, что язык Rust говорит: "я тут обязательно девиртуализую, а если тебе не надо, то скажи мне об этом".

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

    Комментарий для себя, для закладки (с Хабра): 01:00:28

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

    1:19:52 начал сомневаться - interface segregation или interface separation, вроде бы правильно segregation

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

      Внезапно действительно segregation. То есть это не я ошибался, это я правильно помнил на автомате, но почему-то неправильно записал для слайдов =)

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

    Никак не получалось на словах понять принцип инверсии зависимостей - в чем заключается инверсия? Пока не нарисовал для себя картинку:
    Объекты высокого уровня (бизнес-логика) .---> Интерфейсы

  • @ДаниилСавченко-з1ю

    Слайд 4:
    virtual ~IM() = 0; должно быть, думаю, default. Подзреваю, просто опечатка (ну или трюк, если clone почему-то намерено не сделана чисто вирутальной, и чистую виртуальность обеспечивает деструктор, для которого, тем не менее, где-то есть реализация). Плюс не очень понятно, зачем в clone передавать еще одну матрицу (и что будет, если он будет не того типа). Но это так, придирки, в целом то идея ясна. А вот...
    Слайды 27-29:
    Грусть-печаль. Автор, по всему очень знающий специалист и замечательный лектор, кажется, наступил на... одну из классических мин C++.
    В функцию draw, принимаюущую указатель на массов Shape, НЕЛЬЗЯ передавать указатель на массив Rectangle. Чтоб понять почему, нужно написать любую разумную реализацию этой функции, где, видимо, будет код типа shapes[i], и подумать над тем, что будет происходить, если Rectangle добавил относительно Shape новое поле и размер наследника больше, чем у Shape.
    Я совсем не эксперт, но подозреваю, что корень того, почему так делать нельзя, лежит как раз в плоскости размышлений, включающих слово "ковариантность" (которой и посвящены слайды).
    Только указатели на одинарные объекты в плюсах корректно преобразовывать по иерархии.
    P.S. Смотрю магистерский курс - лекции в целом правда отличные.

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

      Если не секрет, а что будет, если я вызываю через указатели и функции вириуальные?

    • @ДаниилСавченко-з1ю
      @ДаниилСавченко-з1ю Рік тому +4

      @@tilir Я может быть не совсем понял вопрос. Если вы обращаетесь только к первому элементу массива, то, разумеется, все будет хорошо. Как при обращении к членам базового класса, так и к вирутальным/не виртуальным функциям. Проблема же возникает именно при итерировании: shapes[i] должно знать на сколько сместиться и он смещает на размер базового класса * i, если по указателю реально массив наследников и у наследника размер другой, то shapes[1] дает указатель где-то посередине первого (по индексу 0) rectangle в массиве. Попытка позвать на нем хоть что-нибудь должна закончиться печально и в общем случае непредсказуемо godbolt.org/z/8qfW31MMs

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

      Интересный консерн. Спасибо я об этом действительно не подумал. Я говорил о синтаксической возможности так сделать. Кажется я знаю как улучшить эти слайды: рекомендацией так не делать ))

  • @quicklilac
    @quicklilac 4 місяці тому +1

    Спасибо за лекцию.
    Отличная презентация от Шона Парента по одной из затронутых тем: ua-cam.com/video/QGcVXgEVMJg/v-deo.html (полиморфизм без наследования, “runtime concept idiom”).

    • @quicklilac
      @quicklilac 2 місяці тому

      А вернее даже лучше так: “type erasure.”

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

    Спасибо большое за лекцию, очень интересно было смотреть. Пример с Parent Reversal - это вообще красота. На 24-м слайде вы говорили, что есть другие техники совмещения SRP и OCP. Не могли бы вы подсказать, где можно поискать другие методики выполнения обоих принципов?

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

      Есть отличный доклад Иглебергера, упомнятый мной в т.ч. в литературе: ua-cam.com/video/Ntraj80qN2k/v-deo.html
      Оттуда дальше можно идти по ссылкам.

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

      @@tilir Спасибо большое!

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

      Parent Reversal вроде как = visitor

  • @Сергей-з3б6в
    @Сергей-з3б6в 11 місяців тому

    Если честно, то я не понял зачем там friend (class Drawable в районе 01:00:00)

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

    МакКоннел - 93й год, Банда Четырех - 94й год, как они могли быть написаны с упором на Java, если Java официально вышла только в 95м? Понятно, что эта технология какое-то время разрабатывалась в недрах Sun Microsystems, но не уверен, что авторы этих книг про нее знали да ещё и в деталях. Скорее это С++ тогда такой был, шаблоны появились в 91-м году, STL в 92-93. Книгу МакКоннела читал давно и явно не первое издание, Вики пишет, что в первом издании примеры были на С, Basic и Pascal. Вообще наверное такая эволюция языка С++, особенно начиная с 11го стандарта, свидетельствует, что знать о паттернах надо, но заучивать их реализацию не стоит, завтра в язык завезут новые возможности (те же лямбды или pattern matching) и какие-то паттерны отомрут, а другие станут реализовываться в одну строчку. Паттерны это скорее наш программистский фольклор или часть языка общения. Но на собеседованиях все ещё спрашивают хотя бы названия (натурально, перечислите все паттерны проектирования, которые знаете).

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

      Я про классическое второе издание (2003 кажется) которое было существенно обновлено и там примеры либо на Java либо на таком C++ который тоже Java.

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

      @@tilir Тогда соглашусь, просто в списке литературы вы ссылались на первое издание, поэтому и удивился.

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

    1:04:00 строка 79, вы объявили draw у Drawable как friend-функцию, а не метод класса. Это сделано для унификации с другими функциями draw чтобы можно было наворачивать рекурсивные структуры типа DrawableObject или по каким-то другим причинам? Просто из других ваших лекций сложилось представление, что вы не рекомендуете использовать friend'ы.

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

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

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

      Спасибо за ответ и за лекцию конечно. Мощная штука тогда получилась, хотя я долго думал и не смог понять зачем может понадобиться строить DrawableObject если что угодно, для чего есть функция draw, можно сразу обернуть в DrawableObject.

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

    1:00:32 Конструктор +Drawable(x:int) оставлен по ошибке?

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

    Не совсем понятно по поводу функции IM& IM::clone(const IM&) 4:06 . Параметр же инвариантен, только возвращающее значение контрвариантно, какая реализация может сохранить нас от не правильного копирования, на ум только реализация с dynamic_cast приходит?

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

      Она вызывается у объекта наследника который знает как себя копировать. Другое дело что ей вообще-то не нужен параметр. Ну или можно сделать её статической. Виртуальная функция clone с параметром это опечатка.

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

    А какие темы будут в следующем семестре.?

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

      Я написал пост про это на вкладке сообщества: ua-cam.com/users/postUgkxZb0KXNeVaGkYhTvh_kqFArV9WMJEEaqf

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

    Не раскрыта тема противоречия хорошей архитектуры и производительности, скажем в первом случае появляется копирование, а с интерфейсами разбитыми на N виртуальных интерфейсов страшно смотреть на sizeof
    P.S. объяснение про зависимости от интерфейсов лучше на шаблонах наверное крутить, т.к. ... Ну лучше так.
    Например более менее одинаковые интерфейсы контейнеров стандартных, а использующий их код становится как бы шаблонным, т.к. можно подменить один контейнер на другой и код скомпилируется(если "удовлетворял интерфейсу" например ассоциативного контейнера)
    Короче говоря в некотором роде стандартная либа стимулирует к написанию хорошего кода и делает сложным написание плохого кода, важный момент
    P.P.S из getpath хотелось бы возвращать std::filesystem::path ... Что как бы примерно того же уровня абстракции что и директория

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

      У вас такой, российский подход к тому, как выразить свое мнение по поводу работы другого человека. Надо несколько иначе, вот так, например - "Константин, большое спасибо за отличную лекцию. Мне особенно понравился слайд 38, где вы говорите о необходимости быть вежливым, в целом - доброжелательным, при обсуджении работы другого человека. Не могли бы вы еще затронуть вопросы производительности в связке с принципами проектирования? Было бы интересно познакомиться с вашим опытом. Еще раз спасибо".

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

      @@andreysolovyev630 это провоцирует плохое проектирование человеческих отношений, лучше подразумевать, иметь договор, что люди пишут тем, от кого хотят получить ответ в том или ином виде, например в виде улучшения качества контента, а если хотят улучшить качество, это не значит что оно плохое, просто хотят развития мира в сторону улучшения

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

      @@andreysolovyev630 это шутка?

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

      Мне кажется, хорошая архитектура всегда принимает в расчёт производительность. Противоречие есть между производительностью и слепым выполнением абстрактных принципов, это да =)
      Про стандартную библиотеку будет подробно в следующем семестре, здесь мы подытоживаем классику ООП.
      Про std::filesystem я вроде упомянул...

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

      У меня не такие высокие стандарты, вопрос ув. Ника Келбона был сформлирован вполне корректно имхо. Но рад, что вам понравился слайд 38 =)

  • @ВячеславВорончихин

    Здравствуйте, у Вас в коде по SRP (~30мин) указаны не константные итераторы begin и end, хотя в цикл передаётся константная ссылка, подскажите как можно заставить цикл для диапазонов брать cbegin и cend?

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

      Увы в [stmt.ranged] есть только три варианта:
      (1) range и range+N
      (2) range.begin() и range.end()
      (3) std::begin(range) и std::end(range)
      Так что правильный ответ -- насколько я понимаю, никак.
      Но есть хорошая новость. В требованиях к стандартных контейнерам указано, что begin() const и cbegin() должны вести себя идентично см. [tab:container.req]
      То есть вы можете просто сделать begin() const возвращающим const iterator (вы ОБЯЗАНЫ его таким сделать) и дальше итерироваться по константному диапазону типа for(auto Elt: std::as_const(Cont)) и т.п.
      Ну и как вы сами понимаете я это и сделал =)
      Я не останавливался на деталях т.к. уже очень скоро будет лекция по контейнерам и итераторам где я расскажу всё это и многое другое.

    • @ВячеславВорончихин
      @ВячеславВорончихин 2 роки тому

      @@tilir Большое спасибо за ответ, лекции как всегда отличные. С нетерпением жду продолжение

  • @АндрейШерстобитов-в8д

    Подскажите, пожалуйста, какой программой Вы пользуетесь при написании UML диаграмм? Нашел только Dia и MS Visio, первый не такой красивый как у Вас, а второй платный.

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

      Я пользуюсь dia. До красивого он настраивается ))

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

    Здравствуйте, на 45:57 упоминается доклад Eagleburger 2017 никак не получается его нагуглить, есть ли ссылка на этот доклад?

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

      Klaus Igleberger

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

      @@tilir Спасибо большое

  • @volodiaagadjanov7087
    @volodiaagadjanov7087 2 роки тому +12

    Обожаю лекции Константина, часто рекомендую начинающим. Хоть вещи рассказываются весьма тривиальные, но, на мой взгляд, это именно то, что дает настоящий драйв и мотивацию изучать языки, их дизайн и философию. Самые интересные вещи в С++ лежат на поверхности.
    + Константин как лектор и диктор весьма состоялся и нашел себя, слушать его интересно и даже зная о предмете разговора - любопытно.

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

    Константин Владимиров крутится - знания мутятся