Очень полезная лекция. Прямо всё самое важное по этой теме. Очень понравилось, как вы рассказываете: и слушать интересно, и всё запоминается, потому что специально акцентируете внимание на важных моментах. Очень круто!
Приятные воспоминания. Второй курс, только не компилятор, а интерпретатор, мощного и лаконичного подмножества русского языка. Жаль только на кафедре к этому отнеслись немного странно. 😉
Спасибо за очередную замечательную лекцию! По поводу замечания в 55:41 писать или не писать virtual. Это от части code-style. Но у привычки писать virtual есть одно важное преимущество: Когда читаешь код, virtual у overrided функций помогают лучше видеть интерфейс класса. virtual всегда идёт перед функцией, его проще увидеть. Разумеется, это замечание не относится к ревью, а только к правильно написанному коду.
@@tilir раз virtual был до, то и override логично было бы сделать до. Единственное, что override как расширение разных компиляторов существовал до включения в стандарт и как раз везде шел после. Ну и кстати, аттрибуты появились в стандарте одновременно с override, и идут перед. Вполне могло быть [[override]], но, видимо, решили сделать как уже устоялось. Просто вдруг были какие-то особые причины, о которых я даже не задумывался..
@@bochkarevartem мне таких причин неизвестно. Но с другой стороны мне и глаза особо не режет, что он после. Я не думаю что это мог бы быть атрибут. Всё-таки override это что-то более фундаментально. Атрибуты обычно касаются только самой функции. Override контролирует свойство семейства классов в которых входит динамически полиморфная функция. Мне вот ещё непонятно почему нельзя было запилить это как ключевое слово...
Спасибо! Вопрос по поводу слайда 24 (48:25). Насколько мне известно работа с vptr все-таки обычно организована несколько иначе. На слайде приведена упрощенная картина для более простого понимания, или есть какие-то специфические имплементации, устроенные таким образом? Насколько мне известно в момент компиляции программы уже известны какие виртуальные функции должна содержать таблица виртуальных функций для каждого класса, поэтому перед выполнением программы все таблицы виртуальных функций уже содержатся в статических данных, а все что делают конструкторы-деструкторы, это просто переставляют указатель vptr конкретных экземпляров классов на эти таблицы. И судя по листингу ассемблера эта таблица не обязана заканчиваться nullptr. Я проверил по нескольким компиляторам и там нигде нет динамической аллокации, а работает именно как я описал. Если предположить что конструктор должен динамически выделять память под vtable, то в голову приходит как минимум пара проблем: 1) должны ли разные экземпляры одного класса разделять одну таблицу, или у каждого экземпляра должен быть свой экземпляр? Если каждый инстанс хранит свою копию таблицы, то это выглядит супер дорого, если нет, то кто отвечает за удаление таблицы. 2) поскольку производные классы могут накидывать дополнительные поля в таблицу виртуальных функций, то базовый класс на момент создания не может знать размер таблицы виртуальных функций. И либо производные классы должны ее переаллоцировать, либо в базовый конструктор нужно еще прокидывать неявный параметр на реальный размер таблицы для этого класса, ну или еще какое-нибудь костыльное решение. Все это выглядит не очень, на фоне просто метода, описанного выше.
Разумеется это очень упрощенная картинка. Спасибо за развернутый комментарий )) Книжка Липмана по деталям объектной модели будет у меня в списке литературы.
1:00:40 В строке ISquare *sq = new Triangle; delete sq; Откуда operator delete знает сколько байт нужно освободить? Почему он освободит sizeof(Triangle), а не sizeof(ISquare)?
Кстати, вспомнил, как можно вызвать деструктор наследника, если есть ссылка на базовый класс и у того деструктор невиртуальный. Есть же правило, что константные ссылки продляют время жизни временных объектов и такой код const Basic &b = make_derived() вполне валиден если make,_derived() возвращает объект класса Derived по значению. Тогда при окончании времени жизни b будет вызван деструктор временного объекта, Derived, а не Basic, несмотря на тип ссылки. И здесь неважно виртуальный деструктор у Basic или нет.
Спасибо за лекции! 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 используются как взаимозаменяемые понятия, а потому не могу уложить их в голове, ведь они означают совершенно разные вещи. Можете что-то сказать по этому поводу?
У нас есть связывание на уровне языка: оно бывает статическим или динамическим (через виртуальные функции). И у нас есть слой под языком, когда статическое (на уровне языка) связывание может реализоваться через механизмы разделяемых библиотек и т.п. Это ортогональные вещи. На этих лекциях я под статическим и динамическим связыванием имею в виду только связывание на уровне языка, независимо от того какие реальные механизмы связывания работают там, под водой.
Как же хорошо, что в С++ union без конструктора это ошибка компиляции. Ведь могли бы выбирать конструктор поля основываясь, например, на прогнозе погоды...
На слайдах 13 и 14 (27:14) видимо произошла путаница для определений double_square. На 13 принимает Square &s, но не использует; а на 14 нет аргументов, но использует s в теле метода. P.S. Огромное спасибо за познавательные лекции!
Добрый вечер. Почему бы на 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() {} };
User-provided vs user-declared, чувствуешь себя немного юристом, когда такое читаешь. "Проценты за месяц начисляются по минимальной сумме, которая была на счёте в этот месяц, если хоть в один день на счёте был ноль, то проценты не начисляются." - А если я открыл счёт второго числа, то первого на нем был ноль? - Нет, первого числа счёт не существовал, а значит на нем не был ноль. Вот какие-то такие аналогии на ум приходят, когда стандарт читаешь.
Здравствуйте, не очень понял на 1:50:00. Порядок инициализации объектов же не зависит от их порядка в списке инициализации? То есть Array будет проинициализирован первым не зависимо от того на каком месте он будет в списке инициализации. И он будет проиницилизирован указателем на непроинициализированную область памяти, и соответственно потом это останется висячим указателем. Но такой код скомпилируется. (Рассуждения для себя, если я ошибаюсь, буду рад если кто нибудь поправит)) Автору канала огромное спасибо за такие лекции!
Здравствуйте. Не совсем понимаю, как функции "хранятся" (понимаю, что не хранятся они там) внутри класса? То есть, по факту, это функции, которые принимают неявно this, но как это реализовано на практике? При использовании пространств имён механизм схожий?
Функция не "хранится внутри класса". Функция-член это просто функция с манглированным именем принимающая this первым неявным аргументом. Пространство имён это плюс к манглированию имени. Я собственно показываю как это устроено в первых лекциях курса. Возможно вы слишком забежали вперёд.
@@tilir это уже больше к красоте. Изменение уровня доступа при переопределении на мой взгляд не очень хорошая практика. Например в языках Java и C# она не разрешена, как и приватные виртуальные методы. Знаю людей, которые при реализации интерфейса, делают все методы приватными, чтобы можно было только через интерфейс вызывать
А если и в базовом классе не тривиальный деструктор и в наследуемом, то как нужно определить деструктор, ведь если мы переопределим деструктор, то базовая не часть утечет
Если создаем абстрактный класс, компилятор выдает варнинг: виртуальная таблица будет создана в каждой единице трансляции. Добавление какой-либо функции с телом в .cpp решает эту проблему. Есть ли другие способы решения этой проблемы?
Можем ли мы добавить в список примеров полиморфизма (перегрузка, шаблоны, виртуальные методы) класс variant с функцией visitor? Это полиморфизм в рантайме по типу хранимого значения, начиная с C++17 является частью стандарта.
Лекция супер! Но мне показалось, что не раскрыт вопрос: а когда использовать статический, а когда динамический полиморфизм? В каких случаях, что применять?
Лекция про проектирование будет отдельно. Сейчас мы просто проходим средства языка. Кое-что о сценариях применения конечно говорится, но паззл пока не собран.
@@Ii_Naomasa_san мне из последних понравилась книга Пикуса "Hands-On Design Patterns with C++" (есть в русском переводе, издана в этом году). Всё очень по делу.
@@tilir О Пикус. Крутой мужик. Несколько раз бывал у него на публичных выступлениях. У него всегда с собой заготовочки нескольких строк кода и бенчей, через которые открываются двери в "Нарнию" =)
Добрый день! Когда станет открытым курс по компиляторостроению? Только из-за вас заинтересовался этой темой. В хорошем смысле, конечно! Спасибо! Читаю "умные книжки", но мне этого не хватает. Хочу когда-нибудь увидеть ваши лекции и задания по курсу. Может быть, можно где-то посмотреть задание для компилятора ParaCL? Что-то вроде методы?
Курс по компиляторам пока не сделан. Очень надеюсь что в ближайшие годы я к нему хотя бы приступлю. Мне есть что сказать на эту тему. Пока что всё, что я читаю, это несколько небольших лекций: ParaCL, clang, LLVM, toolchain, вот это всё.
@@tilir То есть ребята пишут интерпретатор ParaCL со знаниями спецкурса по тулчейнам и курса бакалавров, где некоторые лекции поднимают в какой-то форме вопросы по парсингу, автоматам и всему такому?
19:45 Есть такой простенький вопрос. Насколько корректно в качестве напоминания, писать вот так: struct BinOp : public Node { ///// }; Идея в том, чтобы подчеркнуть сказанное (это как бы поле, которое лежит над структурой производного класса). Конкретно, вопрос в том - какова удобочитаемость? Не сильно ужасно, если я придерживаюсь такого стиля в некоторых местах?
Мне кажется, что наследование в строчку это классика и по умолчанию читатель кода будет искать базовые классы справа по коду, а не снизу. Это сложившаяся норма, ломать её -- себе дороже. Я лично эти проблемы решаю так. Студенты пишут как угодно, а я, когда ко мне приходят их проекты, прогоняю их через индентацию (например clang-format) с дефолтными настройками. Убирая цветущую сложность и уникальные идеи по форматированию, но -- только на своей стороне, не вмешиваясь в творческий процесс и не выламывая руки. Так и вы. Выставите под ваш стиль настройки индента и редактируйте в таком стиле у себя, если вам так удобнее, никто и слова не скажет. Собственно никто и не узнает =)
Доброго времени суток. "Агрегатная иницилизация работает если все поля в одной секции" на ~21 минуте. godbolt - z/h5sGqYcM4 У меня при приватной части ошибку выдает, хотя от туда от куда инициализирую права доступа есть. Заранее спасибо!
Это меня что-то проглючило. По [dcl.init.aggr] An aggregate is an array or a class with [...] no private or protected direct non-static data members кроме всего прочего. Но я даже не могу внести это в errata так как лекция не про это и того нет на слайдах. Тем не менее спасибо за внимательность.
Не встречал на русском более качественного курса чем у Вас. Большое спасибо за труд :-)
Очень полезная лекция. Прямо всё самое важное по этой теме. Очень понравилось, как вы рассказываете: и слушать интересно, и всё запоминается, потому что специально акцентируете внимание на важных моментах. Очень круто!
Какие же крутые лекции! Но как же хотелось бы увидеть лекции по си в вашем исполнении!
1:34:41 Паттерн "Public Морозов"
Спасибо за лекции! Безумно интересно)
Есть опечатка в слайде №54 (1:35:08 ) строки struct Derived : public Base, необходимо : public BaseNVI
Приятные воспоминания. Второй курс, только не компилятор, а интерпретатор, мощного и лаконичного подмножества русского языка. Жаль только на кафедре к этому отнеслись немного странно. 😉
Спасибо за очередную замечательную лекцию!
По поводу замечания в 55:41 писать или не писать virtual. Это от части code-style. Но у привычки писать virtual есть одно важное преимущество:
Когда читаешь код, virtual у overrided функций помогают лучше видеть интерфейс класса.
virtual всегда идёт перед функцией, его проще увидеть.
Разумеется, это замечание не относится к ревью, а только к правильно написанному коду.
Мне кажется override это довольно длинная и заметная аннотация...
@@tilir кстати, почему override решили сделать после функции? Казалось бы, лучше было просто перед функцией вместо virtual ставить override...
@@bochkarevartem большинство аннотаций после. Мне скорее удивительно что virtual до.
@@tilir раз virtual был до, то и override логично было бы сделать до. Единственное, что override как расширение разных компиляторов существовал до включения в стандарт и как раз везде шел после. Ну и кстати, аттрибуты появились в стандарте одновременно с override, и идут перед. Вполне могло быть [[override]], но, видимо, решили сделать как уже устоялось. Просто вдруг были какие-то особые причины, о которых я даже не задумывался..
@@bochkarevartem мне таких причин неизвестно. Но с другой стороны мне и глаза особо не режет, что он после.
Я не думаю что это мог бы быть атрибут. Всё-таки override это что-то более фундаментально. Атрибуты обычно касаются только самой функции. Override контролирует свойство семейства классов в которых входит динамически полиморфная функция.
Мне вот ещё непонятно почему нельзя было запилить это как ключевое слово...
Спасибо!
Вопрос по поводу слайда 24 (48:25).
Насколько мне известно работа с vptr все-таки обычно организована несколько иначе. На слайде приведена упрощенная картина для более простого понимания, или есть какие-то специфические имплементации, устроенные таким образом?
Насколько мне известно в момент компиляции программы уже известны какие виртуальные функции должна содержать таблица виртуальных функций для каждого класса, поэтому перед выполнением программы все таблицы виртуальных функций уже содержатся в статических данных, а все что делают конструкторы-деструкторы, это просто переставляют указатель vptr конкретных экземпляров классов на эти таблицы. И судя по листингу ассемблера эта таблица не обязана заканчиваться nullptr.
Я проверил по нескольким компиляторам и там нигде нет динамической аллокации, а работает именно как я описал.
Если предположить что конструктор должен динамически выделять память под vtable, то в голову приходит как минимум пара проблем:
1) должны ли разные экземпляры одного класса разделять одну таблицу, или у каждого экземпляра должен быть свой экземпляр? Если каждый инстанс хранит свою копию таблицы, то это выглядит супер дорого, если нет, то кто отвечает за удаление таблицы.
2) поскольку производные классы могут накидывать дополнительные поля в таблицу виртуальных функций, то базовый класс на момент создания не может знать размер таблицы виртуальных функций. И либо производные классы должны ее переаллоцировать, либо в базовый конструктор нужно еще прокидывать неявный параметр на реальный размер таблицы для этого класса, ну или еще какое-нибудь костыльное решение. Все это выглядит не очень, на фоне просто метода, описанного выше.
Разумеется это очень упрощенная картинка. Спасибо за развернутый комментарий )) Книжка Липмана по деталям объектной модели будет у меня в списке литературы.
На задании 1:50:30 может, можно проинициализировать IBuffer нашим MyBuffer? 😅Или это вызовет проблемы, которые я не вижу?(
1:00:40
В строке ISquare *sq = new Triangle; delete sq;
Откуда operator delete знает сколько байт нужно освободить? Почему он освободит sizeof(Triangle), а не sizeof(ISquare)?
Кстати, вспомнил, как можно вызвать деструктор наследника, если есть ссылка на базовый класс и у того деструктор невиртуальный. Есть же правило, что константные ссылки продляют время жизни временных объектов и такой код const Basic &b = make_derived() вполне валиден если make,_derived() возвращает объект класса Derived по значению. Тогда при окончании времени жизни b будет вызван деструктор временного объекта, Derived, а не Basic, несмотря на тип ссылки. И здесь неважно виртуальный деструктор у Basic или нет.
Константин, похоже на слайде 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 таже самая опечатка есть на слайде 64.
еще микро опечатка на слайде 57:
...эффективно воВзводить разреженные матрицы...
Спасибо за лекции!
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 используются как взаимозаменяемые понятия, а потому не могу уложить их в голове, ведь они означают совершенно разные вещи.
Можете что-то сказать по этому поводу?
У нас есть связывание на уровне языка: оно бывает статическим или динамическим (через виртуальные функции). И у нас есть слой под языком, когда статическое (на уровне языка) связывание может реализоваться через механизмы разделяемых библиотек и т.п. Это ортогональные вещи. На этих лекциях я под статическим и динамическим связыванием имею в виду только связывание на уровне языка, независимо от того какие реальные механизмы связывания работают там, под водой.
Как же хорошо, что в С++ union без конструктора это ошибка компиляции. Ведь могли бы выбирать конструктор поля основываясь, например, на прогнозе погоды...
На слайдах 13 и 14 (27:14) видимо произошла путаница для определений double_square. На 13 принимает Square &s, но не использует; а на 14 нет аргументов, но использует s в теле метода.
P.S. Огромное спасибо за познавательные лекции!
Спасибо за внимательность. Ошибка на 13-м слайде, там должен быть просто метод.
Имеет ли смысл внутри дочернего класса вызвать using родительского?
как на 1:43:23 using Matrix; вместо using Matrix::pow;
Контест по си накрылся( ну, хоть успел все прорешать, кроме пары первых заданий из первого семинара
51:24 Опечатка "SparceMatrix" - должно быть "SparseMatrix". Отследить что-то кроме орфографических ошибок компетенции не хватает
Тоже неплохо, спасибо, поправлю на слайдах.
Добрый вечер. Почему бы на 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() {}
};
Не знал что так можно. Это несколько реабилитирует union как технику в C++.
Оставлю для памяти: godbolt.org/z/f5cTe9v55
User-provided vs user-declared, чувствуешь себя немного юристом, когда такое читаешь. "Проценты за месяц начисляются по минимальной сумме, которая была на счёте в этот месяц, если хоть в один день на счёте был ноль, то проценты не начисляются." - А если я открыл счёт второго числа, то первого на нем был ноль? - Нет, первого числа счёт не существовал, а значит на нем не был ноль. Вот какие-то такие аналогии на ум приходят, когда стандарт читаешь.
Я обычно оставляю такое за кадром базового курса. Но тут как-то уж очень к слову пришлось...
Здравствуйте, не очень понял на 1:50:00. Порядок инициализации объектов же не зависит от их порядка в списке инициализации? То есть Array будет проинициализирован первым не зависимо от того на каком месте он будет в списке инициализации. И он будет проиницилизирован указателем на непроинициализированную область памяти, и соответственно потом это останется висячим указателем. Но такой код скомпилируется. (Рассуждения для себя, если я ошибаюсь, буду рад если кто нибудь поправит)) Автору канала огромное спасибо за такие лекции!
Нет, порядок инициализации подобъектов не зависит от их порядка в списке инициализации, а только от порядка внутри определения класса.
@@tilir Ага, понял, спасибо
Здравствуйте.
Не совсем понимаю, как функции "хранятся" (понимаю, что не хранятся они там) внутри класса? То есть, по факту, это функции, которые принимают неявно this, но как это реализовано на практике?
При использовании пространств имён механизм схожий?
Функция не "хранится внутри класса". Функция-член это просто функция с манглированным именем принимающая this первым неявным аргументом. Пространство имён это плюс к манглированию имени. Я собственно показываю как это устроено в первых лекциях курса. Возможно вы слишком забежали вперёд.
1:35:21, слайд 53, наверно имелось в виду struct Derived : BaseNVI
Ну и можно было сделать foo_impl protected в обоих классах
Да действительно, досадная опечатка. А зачем protected?
@@tilir это уже больше к красоте. Изменение уровня доступа при переопределении на мой взгляд не очень хорошая практика. Например в языках Java и C# она не разрешена, как и приватные виртуальные методы.
Знаю людей, которые при реализации интерфейса, делают все методы приватными, чтобы можно было только через интерфейс вызывать
А если и в базовом классе не тривиальный деструктор и в наследуемом, то как нужно определить деструктор, ведь если мы переопределим деструктор, то базовая не часть утечет
Если создаем абстрактный класс, компилятор выдает варнинг: виртуальная таблица будет создана в каждой единице трансляции. Добавление какой-либо функции с телом в .cpp решает эту проблему. Есть ли другие способы решения этой проблемы?
Можем ли мы добавить в список примеров полиморфизма (перегрузка, шаблоны, виртуальные методы) класс variant с функцией visitor? Это полиморфизм в рантайме по типу хранимого значения, начиная с C++17 является частью стандарта.
std::visit это довольно обычный compile-time полиморфизм. Там нет стирания типов и нет необходимости ничего решать в рантайме.
Спасибо за лекции! Правда я не до конца понял зачем нужны пустые классы и зачем EBCO и в каких случаях это используется🙁
Стоило мне попробовать поиграть с этим - вопрос разрешился сам собой. Гениальный эксплоит
@@wildboar5565харош, жги
Слайд 63.
Хотелось бы поумничать ). Если Part - пустой класс, то при композиции размер Whole будет больше, чем при наследовании.
EBCO тоже разбирается на этой лекции. То есть если он пустой все знают что делать.
Лекция супер! Но мне показалось, что не раскрыт вопрос: а когда использовать статический, а когда динамический полиморфизм? В каких случаях, что применять?
Лекция про проектирование будет отдельно. Сейчас мы просто проходим средства языка. Кое-что о сценариях применения конечно говорится, но паззл пока не собран.
@@tilir Ясно. Понятно). Может быть подскажите литературу, где можно найти ответ на этот вопрос. Был бы очень благодарен :)
@@Ii_Naomasa_san мне из последних понравилась книга Пикуса "Hands-On Design Patterns with C++" (есть в русском переводе, издана в этом году). Всё очень по делу.
@@tilir О Пикус. Крутой мужик. Несколько раз бывал у него на публичных выступлениях. У него всегда с собой заготовочки нескольких строк кода и бенчей, через которые открываются двери в "Нарнию" =)
Добрый день!
Когда станет открытым курс по компиляторостроению? Только из-за вас заинтересовался этой темой. В хорошем смысле, конечно! Спасибо! Читаю "умные книжки", но мне этого не хватает. Хочу когда-нибудь увидеть ваши лекции и задания по курсу. Может быть, можно где-то посмотреть задание для компилятора ParaCL? Что-то вроде методы?
Курс по компиляторам пока не сделан. Очень надеюсь что в ближайшие годы я к нему хотя бы приступлю. Мне есть что сказать на эту тему. Пока что всё, что я читаю, это несколько небольших лекций: ParaCL, clang, LLVM, toolchain, вот это всё.
@@tilir То есть ребята пишут интерпретатор ParaCL со знаниями спецкурса по тулчейнам и курса бакалавров, где некоторые лекции поднимают в какой-то форме вопросы по парсингу, автоматам и всему такому?
Нет, они пишут интерпретатор со знаниями в объёме iLab второго курса, который я сейчас и выкладываю.
19:45 Есть такой простенький вопрос.
Насколько корректно в качестве напоминания, писать вот так:
struct BinOp
:
public Node
{
/////
};
Идея в том, чтобы подчеркнуть сказанное (это как бы поле, которое лежит над структурой производного класса).
Конкретно, вопрос в том - какова удобочитаемость?
Не сильно ужасно, если я придерживаюсь такого стиля в некоторых местах?
Мне кажется, что наследование в строчку это классика и по умолчанию читатель кода будет искать базовые классы справа по коду, а не снизу. Это сложившаяся норма, ломать её -- себе дороже.
Я лично эти проблемы решаю так. Студенты пишут как угодно, а я, когда ко мне приходят их проекты, прогоняю их через индентацию (например clang-format) с дефолтными настройками. Убирая цветущую сложность и уникальные идеи по форматированию, но -- только на своей стороне, не вмешиваясь в творческий процесс и не выламывая руки.
Так и вы. Выставите под ваш стиль настройки индента и редактируйте в таком стиле у себя, если вам так удобнее, никто и слова не скажет. Собственно никто и не узнает =)
@@tilir хорошо, спасибо.
И спасибо за хорошие лекции! Очень вкусная пища для мозга)
это сложно
да... хак на хаке и хаком погоняет
хороший курс лекций, но как с этим жить?
Доброго времени суток. "Агрегатная иницилизация работает если все поля в одной секции" на ~21 минуте. godbolt - z/h5sGqYcM4 У меня при приватной части ошибку выдает, хотя от туда от куда инициализирую права доступа есть. Заранее спасибо!
Это меня что-то проглючило. По [dcl.init.aggr] An aggregate is an array or a class with [...] no private or protected direct non-static data members кроме всего прочего. Но я даже не могу внести это в errata так как лекция не про это и того нет на слайдах. Тем не менее спасибо за внимательность.
@@tilir Спасибо