Приветственный пост
Рады приветствовать всех на нашем канале!
Вы устали от скучного, монотонного, обезличенного контента по плюсам?
Тогда мы идем к вам!
Здесь не будет бесполезных 30 IQ постов, сгенеренных ChatGPT, накрученных подписчиков и активности.
Канал ведут два сеньора, Денис и Владимир, которые искренне хотят делится своими знаниями по С++ и создать самое уютное коммьюнити позитивных прогеров в телеге!
(ну вы поняли, да? с++, плюс плюс, плюс типа
позитивный?.. ай ладно)
Жмакай и попадешь в наш чат. Там обсуждения не привязаны к постам, можете общаться на любые темы.
Материалы для новичка
ГАЙДЫ:
Мини-гайд по собеседованиям
Гайд по тестовым заданиям
Гайд по категория выражения и мув-семантике
Гайд по inline
Дальше пойдет список хэштегов, которыми вы можете пользоваться для более удобной навигации по каналу и для быстрого поиска группы постов по интересующей теме:
#algorithms
#datastructures
#cppcore
#stl
#goodoldc
#cpp11
#cpp14
#cpp17
#cpp20
#commercial
#net
#database
#hardcore
#memory
#goodpractice
#howitworks
#NONSTANDARD
#interview
#digest
#OS
#tools
#optimization
#performance
#fun
#compiler
#multitasking
#design
#exception
#guide
#задачки
#base
#quiz
#concurrency
Рады приветствовать всех на нашем канале!
Вы устали от скучного, монотонного, обезличенного контента по плюсам?
Тогда мы идем к вам!
Здесь не будет бесполезных 30 IQ постов, сгенеренных ChatGPT, накрученных подписчиков и активности.
Канал ведут два сеньора, Денис и Владимир, которые искренне хотят делится своими знаниями по С++ и создать самое уютное коммьюнити позитивных прогеров в телеге!
(ну вы поняли, да? с++, плюс плюс, плюс типа
позитивный?.. ай ладно)
Жмакай и попадешь в наш чат. Там обсуждения не привязаны к постам, можете общаться на любые темы.
Материалы для новичка
ГАЙДЫ:
Мини-гайд по собеседованиям
Гайд по тестовым заданиям
Гайд по категория выражения и мув-семантике
Гайд по inline
Дальше пойдет список хэштегов, которыми вы можете пользоваться для более удобной навигации по каналу и для быстрого поиска группы постов по интересующей теме:
#algorithms
#datastructures
#cppcore
#stl
#goodoldc
#cpp11
#cpp14
#cpp17
#cpp20
#commercial
#net
#database
#hardcore
#memory
#goodpractice
#howitworks
#NONSTANDARD
#interview
#digest
#OS
#tools
#optimization
#performance
#fun
#compiler
#multitasking
#design
#exception
#guide
#задачки
#base
#quiz
#concurrency
Telegram
Грокаем C++ Chat
You’ve been invited to join this group on Telegram.
Сколько весит объект пустого класса?
Это знание не имеет практически никакого смысла. Как и большинство знаний в этом мире. Однако этот вопрос очень любят интервьюеры, а нормисы-программисты никогда может и не создавали пустой класс(пара практических применений у этого есть, но сейчас не об этом).
Самый очевидный и первый приходящий в голову ответ - 0. Просто 0. Пустой класс, 0. Все сходится. И это очень логично. Однако это не соответствует реальности.
Реальность в том, что пустые классы - такие же классы с точки зрения кода программы и с ними можно делать все то же самое, что и с обычными классами. Например, создать массив объектов пустого класса. Это опять же не имеет смысла. Но для компилятора объект неполиморфного класса с определенным набором методов - тоже пустой. Потому что адреса методов не хранятся в самом классе, они подставляются непосредственно в момент вызова этих методов. А вот такие объекты уже можно хранить. Они хоть что-то делать могут.
Так вот. Если бы объект пустого был размером 0 байт, его невозможно было бы проиндексировать в массиве. Порядковый номер элемента в массиве задает сдвиг этого элемента от начала. Любой сдвиг помноженный на ноль будет равен нулю. Хотя бы уже поэтому любой объект должен хоть сколько-нибудь весить.
Ну и следующий по логике ответ - 1 байт - будет верным. 1 байт это минимальный адресуемый размер памяти (даже тип bool занимает 1 байт), поэтому это довольно логично.
Stay reasonable. Stay cool.
#interview #cppcore
Это знание не имеет практически никакого смысла. Как и большинство знаний в этом мире. Однако этот вопрос очень любят интервьюеры, а нормисы-программисты никогда может и не создавали пустой класс(пара практических применений у этого есть, но сейчас не об этом).
Самый очевидный и первый приходящий в голову ответ - 0. Просто 0. Пустой класс, 0. Все сходится. И это очень логично. Однако это не соответствует реальности.
Реальность в том, что пустые классы - такие же классы с точки зрения кода программы и с ними можно делать все то же самое, что и с обычными классами. Например, создать массив объектов пустого класса. Это опять же не имеет смысла. Но для компилятора объект неполиморфного класса с определенным набором методов - тоже пустой. Потому что адреса методов не хранятся в самом классе, они подставляются непосредственно в момент вызова этих методов. А вот такие объекты уже можно хранить. Они хоть что-то делать могут.
Так вот. Если бы объект пустого был размером 0 байт, его невозможно было бы проиндексировать в массиве. Порядковый номер элемента в массиве задает сдвиг этого элемента от начала. Любой сдвиг помноженный на ноль будет равен нулю. Хотя бы уже поэтому любой объект должен хоть сколько-нибудь весить.
Ну и следующий по логике ответ - 1 байт - будет верным. 1 байт это минимальный адресуемый размер памяти (даже тип bool занимает 1 байт), поэтому это довольно логично.
Stay reasonable. Stay cool.
#interview #cppcore
Виртуальный деструктор
#новичкам
Возможно САМЫЙ популярный вопрос на собеседованиях на джунов и мидлов. Оно и справедливо в принципе: очень простой и понятный вопрос, но ответ на него требует хорошего уровня понимания ООП в принципе и как оно конкретно работает в плюсах. Динамический полиморфизм, наследование, порядок вызовов конструкторов и деструкторов - да, это база. Но именно ее и нужно проверить у начинающих и продолжающих разработчиков.
Обычно они заходят немного издалека и просят вангануть, что выведется в консоль для примерно такого кода:
Вроде ничего сложного, но вот надо все штуки вспомнить, как там объекты создаются, в каком порядке что вызывается. Для начинающих тут часто затупки начинаются.
В коде вроде все хорошо написано и невнимательный кандидат может выдать вот это:
Вот тут-то его и подловили! На самом деле никакого деструктора наследника вызвано не будет и соответственно ресурсы не освободятся. Интервьюер дает наводку посмотреть на деструктор базового класса. И кандидат с красным лицом кричит: "Деструктор - невиртуальный! По указателю на базовый класс вызовется сразу деструктор базового класса, а деструктор дочернего не вызовется. Будет утечка памяти". Его так на курсах научили говорить. И дальше он выдает правильный вывод программы.
И тут интервьюер говорит: "А что будет, если наследник не будет содержать никаких полей? Какие проблемы будут у этого кода?".
И молодой разработчик в ступоре: он же знает, что невиртуальный деструктор приводит к утечкам. Но тут вроде как и утекать нечему. И говорит, что вроде как и проблем не будет.
Естественно, это неправда.
Если с виду ничего плохого не может произойти и даже при запуске программы ничего плохого не происходит - это не значит, что в программе нет проблем. Стандарт говорит:
Отсутствие виртуального деструктора при удалении через базовый класс приводит к неопределенному поведению. И точка. Можете даже больше не упоминать утечки. Потому что может память утечь, а может и Пентагон задудосится от такой программы. Никто не знает.
Для корректного поведения полиморфных объектов и вызова деструктора дочернего класса вам обязательно нужен виртуальный деструктор базового класса.
Часто встречал эту проблему у младших разработчиков, да и сам я спотыкался на этом. Но теперь наши подписчики вооружены и опасны!
Stay armed. Stay cool.
#cppcore #interview
#новичкам
Возможно САМЫЙ популярный вопрос на собеседованиях на джунов и мидлов. Оно и справедливо в принципе: очень простой и понятный вопрос, но ответ на него требует хорошего уровня понимания ООП в принципе и как оно конкретно работает в плюсах. Динамический полиморфизм, наследование, порядок вызовов конструкторов и деструкторов - да, это база. Но именно ее и нужно проверить у начинающих и продолжающих разработчиков.
Обычно они заходят немного издалека и просят вангануть, что выведется в консоль для примерно такого кода:
struct Resource {
Resource() { std::cout << "Resourse has been acquired\n";}
~Resource() { std::cout << "Resource has been released\n";}
};
struct Base {
Base() { std::cout << "Base Constructor Called\n";}
~Base() { std::cout << "Base Destructor called\n";}
};
struct Derived1: Base {
Derived1() {
ptr = std::make_unique<Resource>();
std::cout << "Derived constructor called\n";
}
~Derived1() {std::cout << "Derived destructor called\n";}
private:
std::unique_ptr<Resource> ptr;
};
int main() {
Base *b = new Derived1();
delete b;
}
Вроде ничего сложного, но вот надо все штуки вспомнить, как там объекты создаются, в каком порядке что вызывается. Для начинающих тут часто затупки начинаются.
В коде вроде все хорошо написано и невнимательный кандидат может выдать вот это:
Base Constructor Called
Resourse has been acquired
Derived constructor called
Derived destructor called
Resource has been released
Base Destructor called
Вот тут-то его и подловили! На самом деле никакого деструктора наследника вызвано не будет и соответственно ресурсы не освободятся. Интервьюер дает наводку посмотреть на деструктор базового класса. И кандидат с красным лицом кричит: "Деструктор - невиртуальный! По указателю на базовый класс вызовется сразу деструктор базового класса, а деструктор дочернего не вызовется. Будет утечка памяти". Его так на курсах научили говорить. И дальше он выдает правильный вывод программы.
И тут интервьюер говорит: "А что будет, если наследник не будет содержать никаких полей? Какие проблемы будут у этого кода?".
И молодой разработчик в ступоре: он же знает, что невиртуальный деструктор приводит к утечкам. Но тут вроде как и утекать нечему. И говорит, что вроде как и проблем не будет.
Естественно, это неправда.
Если с виду ничего плохого не может произойти и даже при запуске программы ничего плохого не происходит - это не значит, что в программе нет проблем. Стандарт говорит:
if the static type of the object to be deleted
is different from its dynamic type, the static type
shall be a base class of the dynamic type of
the object to be deleted and the static type
shall have a virtual destructor or
the behavior is undefined.
Отсутствие виртуального деструктора при удалении через базовый класс приводит к неопределенному поведению. И точка. Можете даже больше не упоминать утечки. Потому что может память утечь, а может и Пентагон задудосится от такой программы. Никто не знает.
Для корректного поведения полиморфных объектов и вызова деструктора дочернего класса вам обязательно нужен виртуальный деструктор базового класса.
Часто встречал эту проблему у младших разработчиков, да и сам я спотыкался на этом. Но теперь наши подписчики вооружены и опасны!
Stay armed. Stay cool.
#cppcore #interview
Виртуальный конструктор
#новичкам
Виртуальные методы - это основа полиморфизма, одного из важнейших концептов объектно-ориентированного программирования, который позволяет нам реализовывать сложные конструкции из классов и строить гибкую-расширяемую архитектуру. Но вот если на секунду задуматься: конструктор - это ведь тоже метод. Можем ли мы сделать конструктор класса виртуальным?
Один из тех самых популярных вопросов на собеседованиях, который не проверяет никаких практически применимых знаний. Он скорее направлен больше на понимание концепции ООП и механизма создания объектов в С++.
Ответ: нет, не можем. Логика тут довольно простая. Виртуальные функции подразумевают собой позднее связывание объекта и вызываемого метода в рантайме. То есть для них нужны объекты(точнее vptr, которых находится внутри них). А объекты создаются в рантайме. И для создания объектов нужны констукторы. Получается, если бы конструкторы были виртуальными, тособака постоянно гналась бы укусить себя за жёпу получился бы цикл и парадокс(фанатам Шарифова посвящается). Нет указателя на виртуальную таблицу - нет виртуальности.
Если более формально и официально, то вот комментарий самого Бъерна по этому вопросу:
Виртуальный вызов — это механизм выполнения работы при наличии частичной информации. В частности, он позволяет нам вызывать функцию, зная только тип базового класса, а не точный тип объекта. Для создания объекта необходима полная информация. В частности, вам необходимо знать точный тип того, что вы хотите создать. Следовательно, «вызов конструктора» не может быть виртуальным.
Однако, нам по сути этого и не нужно. Нам нужен механизм создания объекта, зависящий от типа полиморфного объекта. И у нас такой механизм есть! Называется он фабричным методом. В ту же степь идет и паттерн "метод clone()".
В сущности, фабричный метод позволяет создавать объекты, тип которых зависит от типа объекта, у которого вызывается метод.
Метод clone позволяет создавать объекты именно того класса, который на самом деле лежит под данным указателем или ссылкой.
Выглядит это так:
У класса Фигура есть метод clone, который позволяет скопировать текущий объект в новый объект. Метод create позволяет дефолтно создать объект того же класса.
В класса Circle эти методы переопределяются. Теперь можно не зная точного типа полиморфного объекта вызвать его конструктор по умолчанию и копирования.
В эти методы также можно добавить аргументов, в том числе и полиморфных типов. Главное, чтобы сигнатуры методов в наследниках и базе совпадали.
Можете кстати порассуждать в комментах, как бы выглядели виртуальные конструкторы и код, который бы их использовал.
Use well-known tools for your task. Stay cool.
#interview #cppcore #pattern
#новичкам
Виртуальные методы - это основа полиморфизма, одного из важнейших концептов объектно-ориентированного программирования, который позволяет нам реализовывать сложные конструкции из классов и строить гибкую-расширяемую архитектуру. Но вот если на секунду задуматься: конструктор - это ведь тоже метод. Можем ли мы сделать конструктор класса виртуальным?
Один из тех самых популярных вопросов на собеседованиях, который не проверяет никаких практически применимых знаний. Он скорее направлен больше на понимание концепции ООП и механизма создания объектов в С++.
Ответ: нет, не можем. Логика тут довольно простая. Виртуальные функции подразумевают собой позднее связывание объекта и вызываемого метода в рантайме. То есть для них нужны объекты(точнее vptr, которых находится внутри них). А объекты создаются в рантайме. И для создания объектов нужны констукторы. Получается, если бы конструкторы были виртуальными, то
Если более формально и официально, то вот комментарий самого Бъерна по этому вопросу:
A virtual call is a mechanism to get
work done given partial information.
In particular, "virtual" allows us to
call a function knowing only an interfaces
and not the exact type of the object.
To create an object you need complete
information. In particular, you need to
know the exact type of what you want to
create. Consequently, a
"call to a constructor" cannot be virtual.
Виртуальный вызов — это механизм выполнения работы при наличии частичной информации. В частности, он позволяет нам вызывать функцию, зная только тип базового класса, а не точный тип объекта. Для создания объекта необходима полная информация. В частности, вам необходимо знать точный тип того, что вы хотите создать. Следовательно, «вызов конструктора» не может быть виртуальным.
Однако, нам по сути этого и не нужно. Нам нужен механизм создания объекта, зависящий от типа полиморфного объекта. И у нас такой механизм есть! Называется он фабричным методом. В ту же степь идет и паттерн "метод clone()".
В сущности, фабричный метод позволяет создавать объекты, тип которых зависит от типа объекта, у которого вызывается метод.
Метод clone позволяет создавать объекты именно того класса, который на самом деле лежит под данным указателем или ссылкой.
Выглядит это так:
class Shape {
public:
virtual ~Shape() { } // A virtual destructor
// ...
virtual std::unique_ptr<Shape> clone() const = 0; // Uses the copy constructor
virtual std::unique_ptr<Shape> create() const = 0; // Uses the default constructor
};
class Circle : public Shape {
public:
std::unique_ptr<Shape> clone() const override;
std::unique_ptr<Shape> create() const override;
// ...
};
std::unique_ptr<Shape> Circle::clone() const { return new Circle(*this); }
std::unique_ptr<Shape> Circle::create() const { return new Circle(); }
У класса Фигура есть метод clone, который позволяет скопировать текущий объект в новый объект. Метод create позволяет дефолтно создать объект того же класса.
В класса Circle эти методы переопределяются. Теперь можно не зная точного типа полиморфного объекта вызвать его конструктор по умолчанию и копирования.
std::unique_ptr<Shape> ptr = std::make_unique<Circle>();
auto new_obj = ptr->create();
auto copy_obj = ptr->copy();
В эти методы также можно добавить аргументов, в том числе и полиморфных типов. Главное, чтобы сигнатуры методов в наследниках и базе совпадали.
Можете кстати порассуждать в комментах, как бы выглядели виртуальные конструкторы и код, который бы их использовал.
Use well-known tools for your task. Stay cool.
#interview #cppcore #pattern
Может ли дружественная функция быть виртуальной?
#опытным
Еще один вопрос из серии "а могут ли рыбы быть рыжими?" - "Да вроде как по цвету могут, но рыжие это обычно про волосы, а волос у рыб нет. Да и вообще, кому это знание нужно?!". Что-то типа такого. Но раз такие вопросы задают, то пора в ваше кунг-фу добавить и этот приемчик.
Давайте начнем с начала.
Дружественная функция - обычная функция, которая имеет доступ к полям класса-друга. И все. Никаких других особенностей у нее нет. И ограничений тоже никаких нет. Ее сигнатура может быть любой. Объект класса-друга может в ней вообще не фигурировать.
А для виртуальности необходим экземпляр класса, в котором укромно затаился vptr, который и отвечает за диспатчинг вызовов в рантайме.
Вот и получается, что виртуальных дружественных функций не бывает.
Конечно вопрос о виртуальных friend-функциях может заходить только тогда, когда в параметрах есть ссылка или указатель на объект полиморфного класса. Но в общем случае на это не накладываются ограничения. Поэтому и в общем случае виртуальных подруг у класса не бывает.
Теперь сможете смело парировать интервьюеровчетким ударом в глазницу быстрым точным ответом
Но осадочек-то остался. Хочется менять работу дружественной функции в зависимости от того, какой конкретный объект ей передается. Такое мероприятие можно организовать достаточно просто, но это в следующем посте.
Have a real friends. Stay cool.
#interview #cppcore
#опытным
Еще один вопрос из серии "а могут ли рыбы быть рыжими?" - "Да вроде как по цвету могут, но рыжие это обычно про волосы, а волос у рыб нет. Да и вообще, кому это знание нужно?!". Что-то типа такого. Но раз такие вопросы задают, то пора в ваше кунг-фу добавить и этот приемчик.
Давайте начнем с начала.
Дружественная функция - обычная функция, которая имеет доступ к полям класса-друга. И все. Никаких других особенностей у нее нет. И ограничений тоже никаких нет. Ее сигнатура может быть любой. Объект класса-друга может в ней вообще не фигурировать.
А для виртуальности необходим экземпляр класса, в котором укромно затаился vptr, который и отвечает за диспатчинг вызовов в рантайме.
Вот и получается, что виртуальных дружественных функций не бывает.
Конечно вопрос о виртуальных friend-функциях может заходить только тогда, когда в параметрах есть ссылка или указатель на объект полиморфного класса. Но в общем случае на это не накладываются ограничения. Поэтому и в общем случае виртуальных подруг у класса не бывает.
Теперь сможете смело парировать интервьюеров
Но осадочек-то остался. Хочется менять работу дружественной функции в зависимости от того, какой конкретный объект ей передается. Такое мероприятие можно организовать достаточно просто, но это в следующем посте.
Have a real friends. Stay cool.
#interview #cppcore
Бросаем число
#новичкам
Мы привыкли, что исключения имеют какую-то свою иерархию и каждый класс имеет свое конкретное назначение в контексте отображения ошибки.
А что если мы попытаемся бросить что-то совсем несвязанное с иcключениями? Например, какой-нибудь тривиальный тип вроде int. Это вообще законно?
Абсолютно законно. В С++ можно бросать все, что угодно, кроме объектов неполных типов, абстрактных классов и указателей на неполный тип. Даже указатель на void можно.
Как и число.
Поймать число примерно также просто, как его бросить:
Это кстати один из любимых вопросов у интервьюеров.
"А можно ли кидать число вместо исключения?"
Теперь вы с полной уверенностью ответите "можно".
Но вот зачем это может быть нужно? Оставьте ваши мысли в комментариях
Make sense. Stay cool.
#interview #cppcore
#новичкам
Мы привыкли, что исключения имеют какую-то свою иерархию и каждый класс имеет свое конкретное назначение в контексте отображения ошибки.
А что если мы попытаемся бросить что-то совсем несвязанное с иcключениями? Например, какой-нибудь тривиальный тип вроде int. Это вообще законно?
Абсолютно законно. В С++ можно бросать все, что угодно, кроме объектов неполных типов, абстрактных классов и указателей на неполный тип. Даже указатель на void можно.
Как и число.
Поймать число примерно также просто, как его бросить:
void foo() {
throw 1;
}
int main() {
try {
foo();
}
catch(int i) {
std::cout << i << std::endl;
}
}
// OUTPUT: 1
Это кстати один из любимых вопросов у интервьюеров.
"А можно ли кидать число вместо исключения?"
Теперь вы с полной уверенностью ответите "можно".
Но вот зачем это может быть нужно? Оставьте ваши мысли в комментариях
Make sense. Stay cool.
#interview #cppcore
new vs malloc
Чем отличаются new и malloc? Один из популярных вопросов на собеседованиях, которые проверяет, насколько хорошо вы знакомы с тонкостями работы с памятью в С/С++. Поэтому давайте сегодня это обсудим.
Не совсем корректно, наверное сравнивать фичи двух разных языков с разными доминантными парадигмами программирования. Но раз в стандарте есть std::malloc, а new тоже выделяет память, то можно попробовать.
👉🏿 new expression помимо аллокации памяти вызывает конструктор объекта. std::malloc только выделяет память.
👉🏿 std::malloc - совсем не типобезопасный. Он возвращает void * без какого-либо признака типа. Придется явно кастовать результат к нужному типу. new в свою очередь возвращает типизированный указатель.
👉🏿 При ошибке выделения памяти new бросает исключение std::bad_alloc, в то время как std::malloc возвращает NULL. Соответственно нужны разные способы обработки ошибочных ситуаций.
👉🏿 Поведение new может быть переопределено внутри кастомных классов, поведение std::malloc - неизменно.
👉🏿 Если вам не нужно конструирование объекта, то просто вызывайте operator new. Он делает то же самое, что и std::malloc(потенциально вызывает его внутри себя).
👉🏿 Для new не нужно вручную высчитывать количество нужных байт. То есть мы не лезем на низкий уровень. Мы заботимся только типе данных, количестве объектов и об аргументах конструктора.
👉🏿 new плохо работает с реаллокациями. Нужно выделить новый сторадж, скопировать туда данные и вызвать delete. В то время, как malloc имеет функцию-партнера realloc, которая может изменить размер существующего куска памяти более эффективно, чем последовательность new-memcpy-delete.
Однако они имеют одну неочевидную схожесть. Нужно стараться по максимуму избегать их явного вызова. Давно придумали умные указатели и контейнеры, которые позволяют максимально освободить разработчика от обязанности ручного управления памятью.
Мы все же современные плюсовики. Поэтому в большинстве случаев, вам не нужны будут прямые вызовы этих функций. В более редких случаях(например кастомные аллокаторы) можно явно использовать new. Ну и в совсем редких случаях(нужда в реаллокации памяти или работа с сишным кодом) можно использовать malloc.
Control your memory. Stay cool.
#cppcore #interview #memory
Чем отличаются new и malloc? Один из популярных вопросов на собеседованиях, которые проверяет, насколько хорошо вы знакомы с тонкостями работы с памятью в С/С++. Поэтому давайте сегодня это обсудим.
Не совсем корректно, наверное сравнивать фичи двух разных языков с разными доминантными парадигмами программирования. Но раз в стандарте есть std::malloc, а new тоже выделяет память, то можно попробовать.
👉🏿 new expression помимо аллокации памяти вызывает конструктор объекта. std::malloc только выделяет память.
👉🏿 std::malloc - совсем не типобезопасный. Он возвращает void * без какого-либо признака типа. Придется явно кастовать результат к нужному типу. new в свою очередь возвращает типизированный указатель.
👉🏿 При ошибке выделения памяти new бросает исключение std::bad_alloc, в то время как std::malloc возвращает NULL. Соответственно нужны разные способы обработки ошибочных ситуаций.
👉🏿 Поведение new может быть переопределено внутри кастомных классов, поведение std::malloc - неизменно.
👉🏿 Если вам не нужно конструирование объекта, то просто вызывайте operator new. Он делает то же самое, что и std::malloc(потенциально вызывает его внутри себя).
👉🏿 Для new не нужно вручную высчитывать количество нужных байт. То есть мы не лезем на низкий уровень. Мы заботимся только типе данных, количестве объектов и об аргументах конструктора.
👉🏿 new плохо работает с реаллокациями. Нужно выделить новый сторадж, скопировать туда данные и вызвать delete. В то время, как malloc имеет функцию-партнера realloc, которая может изменить размер существующего куска памяти более эффективно, чем последовательность new-memcpy-delete.
Однако они имеют одну неочевидную схожесть. Нужно стараться по максимуму избегать их явного вызова. Давно придумали умные указатели и контейнеры, которые позволяют максимально освободить разработчика от обязанности ручного управления памятью.
Мы все же современные плюсовики. Поэтому в большинстве случаев, вам не нужны будут прямые вызовы этих функций. В более редких случаях(например кастомные аллокаторы) можно явно использовать new. Ну и в совсем редких случаях(нужда в реаллокации памяти или работа с сишным кодом) можно использовать malloc.
Control your memory. Stay cool.
#cppcore #interview #memory
Mutable
#новичкам
Это ключевое слово - один из самых темных уголков С++. И не то, чтобы очень важный уголок. Вы вполне ни разу могли с ним не сталкиваться. Но тем не менее по какой-то причине интервьютеры часто задают вопрос: "для чего предназначен mutable?". Ответит человек или нет особо никак не показывает его навыки программиста, лишь знание узких мест языка. Но раз такие вопросы задают, то вы должны быть готовы к ответу на них. Поэтому и родился этот пост.
Проблема вот в чем. Есть константный объект. Как вы знаете, поля константного объекта запрещено изменять. Но это довольно сильное ограничение. Да, не хотелось бы, чтобы инвариаты класса менялись. Однако помимо комплекса полей класса, представляющих собой инвариант класса, в объекте могут храниться другие поля, которые не входят в этот инвариант.
И вот мы имеем дело с тем, что нам хочется иметь семантическую константность, когда защищаем от изменения только те поля, которые должны быть неизменными в константном объекте. Но по дефолту нам дана синтаксическая константность, которая запрещает изменения любых нестатических полей.
В этом логгере мы хотим подсчитать количество логирований на протяжении времени жизни объекта. Одна мы не можем этого сделать, потому что нам запрещено изменять поля в константных методах.
Что же делать?
Вот тут как раз, mutableВалера, настало твое время.
Помечая ключевым словом mutable поле класса вы разрешаете менять его в константных методах:
Теперь мы можем изменять счетчик даже в константном методе.
В целом, на это все о функциональности этого ключевого слова.
В каких кейсах его можно применять?
✅ Сбор статистики вычислений в объекте. Пример выше как раз об этом. Для сбора статистики могут использоваться и более сложные сущности, типа оберток над известными системами мониторинга(аля prometheus).
✅ Если вы хотите потокобезопасные константные методы. Вам могут понадобиться мьютексы и/или кондвары, которые придется пометить mutable, чтобы их можно было использовать в константных методах.
✅ Кэш. Результаты предыдущих вычислений никак не влияют на инвариант класса, поэтому внутренний кэш класса можно пометить mutable, чтобы кэш можно было использовать в константных методах.
Из популярного все. Если кто знает узкий кейсы применения mutable, просим пройти в чат.
Ну все, никакой гадкий интервьюер вас не завалит. Ваше кунг-фу теперь сильнее его кунг-фу.
Surprise your enemy. Stay cool.
#cppcore #interview
#новичкам
Это ключевое слово - один из самых темных уголков С++. И не то, чтобы очень важный уголок. Вы вполне ни разу могли с ним не сталкиваться. Но тем не менее по какой-то причине интервьютеры часто задают вопрос: "для чего предназначен mutable?". Ответит человек или нет особо никак не показывает его навыки программиста, лишь знание узких мест языка. Но раз такие вопросы задают, то вы должны быть готовы к ответу на них. Поэтому и родился этот пост.
Проблема вот в чем. Есть константный объект. Как вы знаете, поля константного объекта запрещено изменять. Но это довольно сильное ограничение. Да, не хотелось бы, чтобы инвариаты класса менялись. Однако помимо комплекса полей класса, представляющих собой инвариант класса, в объекте могут храниться другие поля, которые не входят в этот инвариант.
И вот мы имеем дело с тем, что нам хочется иметь семантическую константность, когда защищаем от изменения только те поля, которые должны быть неизменными в константном объекте. Но по дефолту нам дана синтаксическая константность, которая запрещает изменения любых нестатических полей.
class ThreadSafeLogger {
std::atomic<int> call_count = 0;
public:
void log(const std::string& msg) const {
call_count++; // Error! Changing class field in const member-function
// logging
}
};
В этом логгере мы хотим подсчитать количество логирований на протяжении времени жизни объекта. Одна мы не можем этого сделать, потому что нам запрещено изменять поля в константных методах.
Что же делать?
Вот тут как раз, mutable
Помечая ключевым словом mutable поле класса вы разрешаете менять его в константных методах:
class ThreadSafeLogger {
mutable std::atomic<int> call_count = 0;
public:
void log(const std::string& msg) const {
call_count++; // Works fine
// logging
}
};
Теперь мы можем изменять счетчик даже в константном методе.
В целом, на это все о функциональности этого ключевого слова.
В каких кейсах его можно применять?
✅ Сбор статистики вычислений в объекте. Пример выше как раз об этом. Для сбора статистики могут использоваться и более сложные сущности, типа оберток над известными системами мониторинга(аля prometheus).
✅ Если вы хотите потокобезопасные константные методы. Вам могут понадобиться мьютексы и/или кондвары, которые придется пометить mutable, чтобы их можно было использовать в константных методах.
✅ Кэш. Результаты предыдущих вычислений никак не влияют на инвариант класса, поэтому внутренний кэш класса можно пометить mutable, чтобы кэш можно было использовать в константных методах.
class SomeComputingClass {
mutable std::unordered_map<Key, Result> cache;
public:
Result compute(const Key& key) const {
if (!cache.contains(key)) {
cache[key] = /* actual computing */;
}
return cache[key];
}
};
Из популярного все. Если кто знает узкий кейсы применения mutable, просим пройти в чат.
Ну все, никакой гадкий интервьюер вас не завалит. Ваше кунг-фу теперь сильнее его кунг-фу.
Surprise your enemy. Stay cool.
#cppcore #interview
Еще одно отличие С и С++
#опытным
Продолжаем рубрику, где мы развеиваем миф о том, что С - это подмножество С++. Вот предыдущие части: тык, тык и тык.
В С давно можно инициализировать структуры с помощью так называемой designated initialization. Эта фича позволяет при создании массива или экземпляра структуры указать значения конкретным элементам и конкретным полям с указанием их имени!
Например, хочу я определить разреженный массив из 100 элементов и только 3 их них я хочу инициализировать единичками. Не проблема! В С это можно сделать одной строчкой:
В плюсах такое можно сделать только с помощью нескольких инструкций.
Не так удобно.
Можно даже задавать рэндж значений. Но это правда GNU расширение.
Теперь элементы с 31 по 41 будут инициализированы единичками. Очень удобно!
Для структур задавать значения полям можно вот так:
Нужно обязательно при инициализации указать конкретное поле, которому будет присвоено значение. При чем порядок указания полей неважен! А неупомянутые поля будут инициализированы нулем.
До С++20 в плюсах вообще не было подобного синтаксиса. Начиная с 20-х плюсов при создании объекта класса мы можем аннотировать, каким полям мы присваиваем значение. Но в плюсах намного больше ограничений: поля нужно указывать в порядке объявления в теле класса, никакой инициализации массивов и еще куча тонкостей.
Так что вот вам еще один пример, которым вы сможете парировать интервьюера на вопрос: "верно ли что С - подмножество С++?". Иначе где вам это еще пригодится?
Be different. Stay cool.
#goodoldc #cppcore #cpp20 #interview
#опытным
Продолжаем рубрику, где мы развеиваем миф о том, что С - это подмножество С++. Вот предыдущие части: тык, тык и тык.
В С давно можно инициализировать структуры с помощью так называемой designated initialization. Эта фича позволяет при создании массива или экземпляра структуры указать значения конкретным элементам и конкретным полям с указанием их имени!
Например, хочу я определить разреженный массив из 100 элементов и только 3 их них я хочу инициализировать единичками. Не проблема! В С это можно сделать одной строчкой:
int array[100] = {[13] = 1, [45] = 1, [79] = 1};
В плюсах такое можно сделать только с помощью нескольких инструкций.
int array[100] = {};
array[13] = array[45] = array[79] = 1;
Не так удобно.
Можно даже задавать рэндж значений. Но это правда GNU расширение.
int array[100] = {[13] = 1, [30 ... 40] = 1, [45] = 1, [79] = 1};
Теперь элементы с 31 по 41 будут инициализированы единичками. Очень удобно!
Для структур задавать значения полям можно вот так:
struct point { int x, y, z; };
struct point p1 = { .y = 2, .x = 3 };
struct point p2 = { y: 2, x: 3 };
struct point p3 = { x: 1};
Нужно обязательно при инициализации указать конкретное поле, которому будет присвоено значение. При чем порядок указания полей неважен! А неупомянутые поля будут инициализированы нулем.
До С++20 в плюсах вообще не было подобного синтаксиса. Начиная с 20-х плюсов при создании объекта класса мы можем аннотировать, каким полям мы присваиваем значение. Но в плюсах намного больше ограничений: поля нужно указывать в порядке объявления в теле класса, никакой инициализации массивов и еще куча тонкостей.
Так что вот вам еще один пример, которым вы сможете парировать интервьюера на вопрос: "верно ли что С - подмножество С++?". Иначе где вам это еще пригодится?
Be different. Stay cool.
#goodoldc #cppcore #cpp20 #interview
Что будет если бросить исключение в деструкторе? Ч1
#новичкам
Вопрос, который часто задают на собеседованиях, но боюсь, что мало кто понимает правильный ответ.
Значит единственный адекватный ответ - вызовется std::terminate. И здесь даже не нужно упоминать никаких double exception. То есть:
В этом случае исключение не поймается, а просто вызовется std::terminate. И точка. Никаких дополнений.
Первое надо понимать, что все деструкторы неявно компилятором помечены, как noexcept. То есть не предполагается, что он выбрасывает исключения.
Если вы определяете деструктор дефолтным, то он noexcept. И даже если вы определяете кастомный деструктор, но не указываете ему политику исключений, он все равно помечен noexcept.
Однако мы можем сделать деструктор бросающим. Мы должны явно прописывать политику исключений:
Только в этом случае на консоли появится
И вот здесь уже можно говорить, что будет при раскрутке стека, втором исключении и прочем. Можете сами проверить на годболте.
Пока вы явно не пометили деструктор бросающим, при вылете исключения из деструктор будет вызван std::terminate.
Be explicit in your intentions. Stay cool.
#cppcore #cpp11 #interview
#новичкам
Вопрос, который часто задают на собеседованиях, но боюсь, что мало кто понимает правильный ответ.
Значит единственный адекватный ответ - вызовется std::terminate. И здесь даже не нужно упоминать никаких double exception. То есть:
struct Class {
~Class() {throw 1;}
};
int main() {
try {
{Class cl;}
} catch(...) {
std::cout << "caught exception" << std::endl;
}
}
В этом случае исключение не поймается, а просто вызовется std::terminate. И точка. Никаких дополнений.
Первое надо понимать, что все деструкторы неявно компилятором помечены, как noexcept. То есть не предполагается, что он выбрасывает исключения.
Если вы определяете деструктор дефолтным, то он noexcept. И даже если вы определяете кастомный деструктор, но не указываете ему политику исключений, он все равно помечен noexcept.
Однако мы можем сделать деструктор бросающим. Мы должны явно прописывать политику исключений:
struct Class {
// HERE
~Class() noexcept(false) {throw 1;}
};
int main() {
try {
{Class cl;}
} catch(...) {
std::cout << "caught exception" << std::endl;
}
}
Только в этом случае на консоли появится
caught exception
.И вот здесь уже можно говорить, что будет при раскрутке стека, втором исключении и прочем. Можете сами проверить на годболте.
Пока вы явно не пометили деструктор бросающим, при вылете исключения из деструктор будет вызван std::terminate.
Be explicit in your intentions. Stay cool.
#cppcore #cpp11 #interview
Что будет если бросить исключение в деструкторе? Ч2
#новичкам
Теперь мы разобрались с основным кейсом. Давайте подробнее рассмотрим, что будет, если мы будет работать с явно бросающими деструктором.
Единственный важный вопрос, который здесь можно задать: а что будет, если деструктор вызовется при раскрутке стека?
Вот так:
В этом случае при бросании 1.0, исключение увидит блок catch и перед входом в него начнет раскручивать стек, вызывая деструкторы всех локальных объектов во всех фреймах, которые пролетело исключение.
В нашем коде деструктор Class будет вызван до блока catch и получается, что у нас ситуация, в которой есть 2 необработанных исключения. Эта ситуация называется double exception и она приводит к немедленному вызову std::terminate.
Именно поэтому вы не должны бросать исключения в деструкторах. Именно поэтому все деструкторы по умолчанию noexcept. Потому что невозможно безопасно работать с классом, у которого бросающий деструктор, в мире, где другие сущности тоже могут сгенерировать исключения.
Теперь вы профессионально будете отвечать всем интервьюерам, заслужите много уважения и конфеты(но это не точно).
Stay safe. Stay cool.
#cppcore #cpp11 #interview
#новичкам
Теперь мы разобрались с основным кейсом. Давайте подробнее рассмотрим, что будет, если мы будет работать с явно бросающими деструктором.
Единственный важный вопрос, который здесь можно задать: а что будет, если деструктор вызовется при раскрутке стека?
Вот так:
struct Class {
// HERE
~Class() noexcept(false) {throw 1;}
};
int main() {
try {
Class cl;
throw 1.0;
} catch(...) {
std::cout << "caught exception" << std::endl;
}
}
В этом случае при бросании 1.0, исключение увидит блок catch и перед входом в него начнет раскручивать стек, вызывая деструкторы всех локальных объектов во всех фреймах, которые пролетело исключение.
В нашем коде деструктор Class будет вызван до блока catch и получается, что у нас ситуация, в которой есть 2 необработанных исключения. Эта ситуация называется double exception и она приводит к немедленному вызову std::terminate.
Именно поэтому вы не должны бросать исключения в деструкторах. Именно поэтому все деструкторы по умолчанию noexcept. Потому что невозможно безопасно работать с классом, у которого бросающий деструктор, в мире, где другие сущности тоже могут сгенерировать исключения.
Теперь вы профессионально будете отвечать всем интервьюерам, заслужите много уважения и конфеты(но это не точно).
Stay safe. Stay cool.
#cppcore #cpp11 #interview