Указатель на void
Тот случай, когда тема сишная, но знать это надо и это довольно часто встречается в плюсовых проектах.
Естественно, это все про сишные интерфейсы и C-style функции. Мы все с этим сталкивается, ничего не поделать.
Нетипизированный указатель - очень крутая штука в контексте Си. Он предоставляет возможность до определенной степени использовать обобщенное программирование. Самый очевидный пример - malloc. Он выделяет память заданного размера и выдает указатель на начало этого куска. Функция не знает, как будет использоваться память и под какие структуры. Поэтому перекладывает на программиста ответственность за то, как дальше память будет использована. А программист должен скастовать void* к типизированному указателю, чтобы получить доступ к структуре. Но зачем?
Дело в том, что любой указатель - просто 8-мибайтное число, указывающее на какую-то точку в памяти. Но объекты и структуры - это не точки. Это отрезки. От и до. Поэтому компиллятору надо знать размер области памяти, чтобы достать оттуда информацию. Отсюда и правило, что разыменовывать void* нельзя. Отсюда и второе правило, что адресная арифметика неприменима к void*. Поэтому нужны типизированные указатели. Любой определенный тип имеет вполне конкретный размер, известный компилятору. И когда мы храним указатель на этот тип, мы говорим условному gcc "тут лежит объект и, чтобы откопать его, копай на 4 байта вправо от указателя".
В этом и прикол указателя на воид - если у функции есть такой параметр, мы можем передать туда все что угодно. Отсюда мы имеем 2 основных сценария использования таких указателей.
Первый - дженерик функции, работающие с любым типом. Главное знать размер этого типа. Типичные примеры: стандартный memcpy или какой-нибудь кастомный bytesToHexString
Второй - передача параметров callback функциям, когда типы параметров неизвестны вызывающему коду. Для примера, посмотрите функцию qsort. Она принимает компаратор с двумя параметрами - void*. Это необходимо, так как сама qsort не знает, с какими данными она работает и передает их в callback как есть, в виде void*.
Stay cool.
#cppcore #goodoldc #memory
Тот случай, когда тема сишная, но знать это надо и это довольно часто встречается в плюсовых проектах.
Естественно, это все про сишные интерфейсы и C-style функции. Мы все с этим сталкивается, ничего не поделать.
Нетипизированный указатель - очень крутая штука в контексте Си. Он предоставляет возможность до определенной степени использовать обобщенное программирование. Самый очевидный пример - malloc. Он выделяет память заданного размера и выдает указатель на начало этого куска. Функция не знает, как будет использоваться память и под какие структуры. Поэтому перекладывает на программиста ответственность за то, как дальше память будет использована. А программист должен скастовать void* к типизированному указателю, чтобы получить доступ к структуре. Но зачем?
Дело в том, что любой указатель - просто 8-мибайтное число, указывающее на какую-то точку в памяти. Но объекты и структуры - это не точки. Это отрезки. От и до. Поэтому компиллятору надо знать размер области памяти, чтобы достать оттуда информацию. Отсюда и правило, что разыменовывать void* нельзя. Отсюда и второе правило, что адресная арифметика неприменима к void*. Поэтому нужны типизированные указатели. Любой определенный тип имеет вполне конкретный размер, известный компилятору. И когда мы храним указатель на этот тип, мы говорим условному gcc "тут лежит объект и, чтобы откопать его, копай на 4 байта вправо от указателя".
В этом и прикол указателя на воид - если у функции есть такой параметр, мы можем передать туда все что угодно. Отсюда мы имеем 2 основных сценария использования таких указателей.
Первый - дженерик функции, работающие с любым типом. Главное знать размер этого типа. Типичные примеры: стандартный memcpy или какой-нибудь кастомный bytesToHexString
Второй - передача параметров callback функциям, когда типы параметров неизвестны вызывающему коду. Для примера, посмотрите функцию qsort. Она принимает компаратор с двумя параметрами - void*. Это необходимо, так как сама qsort не знает, с какими данными она работает и передает их в callback как есть, в виде void*.
Stay cool.
#cppcore #goodoldc #memory
Что будет, если malloc'нуть 100 Гигабайт?
У одного моего друга недавно на ревью спросили этот вопрос. Вопрос интересный, и если не знать нюансов, то результат можно только предполагать. Друг накидал вариантов, но ревьюер ответа тоже не знал, поэтому это была задачка на подумать и порассуждать. А я решил не думать, а проверить. Так и родился этот пост.
У меня получилось 2 варианта развития событий: попробовать это провернуть на тачке с привычными характеристиками и на мощном сервере, где RAM много больше 100 Гб. Так уж вышло, что я имею доступ и к одному и к другому. Поэтому раскрою оба варианта.
Чтобы вдруг не заруинить сервер и свою тачку, я попробовал запустить код в онлайн компилляторе wandbox.
В мэйне было 2 строчки:
void *p = malloc(100000000000L); // да да 100 миллиардов байт это не 100 гб, знаю знаю... Jesus Christ...
printf("got %pn", p);
Запустил я это. И даже получил результат. Ничего не упало. Вывод: got (nil). То есть ОС просто отказала маллоку в выдаче такого большого объема памяти. И маллок вернул нулевой указатель. Это кстати одна из причин, почему всегда стоит проверять указатель, возвращенный маллоком, на равенство нулю. Иначе получите segfault.
Далее попробовал на своей локальной машинке. Результат тот же. Никто не хочет мне давать память😭. Но хотя бы комп не сломал.
Ну и наконец, на серваке получил валидный адрес: got 0x7f1e4f2e1010.
Прикольный эксперимент. У меня появились еще пара идей, связанных с выделением большого объема памяти. Буду раскрывать их в будущих постах.
Оставайся на связи. Stay cool.
#fun
У одного моего друга недавно на ревью спросили этот вопрос. Вопрос интересный, и если не знать нюансов, то результат можно только предполагать. Друг накидал вариантов, но ревьюер ответа тоже не знал, поэтому это была задачка на подумать и порассуждать. А я решил не думать, а проверить. Так и родился этот пост.
У меня получилось 2 варианта развития событий: попробовать это провернуть на тачке с привычными характеристиками и на мощном сервере, где RAM много больше 100 Гб. Так уж вышло, что я имею доступ и к одному и к другому. Поэтому раскрою оба варианта.
Чтобы вдруг не заруинить сервер и свою тачку, я попробовал запустить код в онлайн компилляторе wandbox.
В мэйне было 2 строчки:
void *p = malloc(100000000000L); // да да 100 миллиардов байт это не 100 гб, знаю знаю... Jesus Christ...
printf("got %pn", p);
Запустил я это. И даже получил результат. Ничего не упало. Вывод: got (nil). То есть ОС просто отказала маллоку в выдаче такого большого объема памяти. И маллок вернул нулевой указатель. Это кстати одна из причин, почему всегда стоит проверять указатель, возвращенный маллоком, на равенство нулю. Иначе получите segfault.
Далее попробовал на своей локальной машинке. Результат тот же. Никто не хочет мне давать память😭. Но хотя бы комп не сломал.
Ну и наконец, на серваке получил валидный адрес: got 0x7f1e4f2e1010.
Прикольный эксперимент. У меня появились еще пара идей, связанных с выделением большого объема памяти. Буду раскрывать их в будущих постах.
Оставайся на связи. Stay cool.
#fun
std::lower_bound
Продолжение басни с алгоритмами в STL. Предыдущие части можете найти тут жмак и жмак
Сегодня речь пойдет про бинарный поиск. Точнее немного более прикладную его вариацию - std::lower_bound.
На самом деле не удивительно, почему в списке 3-х самых используемых алгоритмов есть этот, учитывая, что на первом месте стояла сортировка. Если что-то и искать, то искать надо в упорядоченных структурах данных. Ведь преимущества очевидны: О(n) против O(logn) (можно и за О(1), но это другая уже другая история). Поэтому следует избегать использования линейного поиска (типа std::find), а использовать подходящий контейнер и эффективный алгоритм поиска над ним.
Функция выполняет поиск в отсортированном диапазоне и возвращает итератор на первый элемент, не меньший, чем заданное значение. За счет того, что поиск происходит в упорядоченном контейнере, алгоритмическая сложность такой операции - О(logn). То есть это очень эффективный способ найти нужный элемент в контейнере. В основе алгоритма лежит бинарный поиск, где на каждой итерации цикла диапазон поиска уменьшается вдвое.
Алгоритм может использоваться, как для поиска конкретного элемента, так и для поиска диапазона значений самостоятельно (открытый диапазон) или в сочетании с upper_bound (закрытый диапазон).
Немного более вариабельным lower_bound становится в применении к деревьям. Тогда возвращаемый итератор можно использовать для эффективной вставки ноды в дерево (например методом map::emplace_hint).
И не зря довольно часто на собесах встречаются задачи на бинарный поиск. Потому что это реально маст-хэв. Поэтому его в принципе полезно знать и уметь запрогать руками.
Stay cool.
#STL #algorithms #datastructures
Продолжение басни с алгоритмами в STL. Предыдущие части можете найти тут жмак и жмак
Сегодня речь пойдет про бинарный поиск. Точнее немного более прикладную его вариацию - std::lower_bound.
На самом деле не удивительно, почему в списке 3-х самых используемых алгоритмов есть этот, учитывая, что на первом месте стояла сортировка. Если что-то и искать, то искать надо в упорядоченных структурах данных. Ведь преимущества очевидны: О(n) против O(logn) (можно и за О(1), но это другая уже другая история). Поэтому следует избегать использования линейного поиска (типа std::find), а использовать подходящий контейнер и эффективный алгоритм поиска над ним.
Функция выполняет поиск в отсортированном диапазоне и возвращает итератор на первый элемент, не меньший, чем заданное значение. За счет того, что поиск происходит в упорядоченном контейнере, алгоритмическая сложность такой операции - О(logn). То есть это очень эффективный способ найти нужный элемент в контейнере. В основе алгоритма лежит бинарный поиск, где на каждой итерации цикла диапазон поиска уменьшается вдвое.
Алгоритм может использоваться, как для поиска конкретного элемента, так и для поиска диапазона значений самостоятельно (открытый диапазон) или в сочетании с upper_bound (закрытый диапазон).
Немного более вариабельным lower_bound становится в применении к деревьям. Тогда возвращаемый итератор можно использовать для эффективной вставки ноды в дерево (например методом map::emplace_hint).
И не зря довольно часто на собесах встречаются задачи на бинарный поиск. Потому что это реально маст-хэв. Поэтому его в принципе полезно знать и уметь запрогать руками.
Stay cool.
#STL #algorithms #datastructures
Как найти минимум в несортированном массиве за константу
Ответ на первый взгляд простой и понятной - хрена с два у тебя такое прокатит. Но что, если вам говорят на собесе : "Вот есть класс стека с методами push_back и pop_back и есть метод min. Нужно, чтобы метод min работал за константное время". Как ни отникивайся, придется решать. Чем мы с вами и займемся. А перед этим делом сами подумайте: как бы вы это сделали? Бывалым ценителям алгоритмических задачек этот вопрос даже каплю пота не вызовет, а вот средних работяг может заставить попотеть.
Первое, что надо понимать - если у вас просто есть готовый массив и ничего больше, то вы не найдете минимум за константное время. Нужно что-то думать. Второе, что надо понимать - очень часто можно довольно просто понизить сложность операции за счет применения правильного хранилища. Например, если превратить массив в неупорядоченное мультимножество, то сложность поиска понижается с О(n) до О(~1). И с этой мысли нужно начинать, когда от вас требуют понизить сложность. Задумайтесь. Не зря нам дали удобную обертку для массива. Мы можем контролировать, как именно будут добавляться и удаляться данные из него, а также можем хранить в этом классе какие-то дополнительные структуры.
В данном случае, решением будет добавить в класс поле current_min_stack - стек, в который будет хранить текущие минимумы для каждого элемента исходного массива. Как только вызывается push_back(value), в current_min_stack добавляется минимум из value и текущей верхушки current_min_stack. На каждый вызов pop_back мы убираем из current_min_stack его последний элемент. А на вызов min мы возвращаем последний элемент из стека текущего минимума.
Не самый эффективный способ, знаем и получше. Вот и напишите в комментах, как улучшить алгоритм.
Stay cool.
#algorithms
Ответ на первый взгляд простой и понятной - хрена с два у тебя такое прокатит. Но что, если вам говорят на собесе : "Вот есть класс стека с методами push_back и pop_back и есть метод min. Нужно, чтобы метод min работал за константное время". Как ни отникивайся, придется решать. Чем мы с вами и займемся. А перед этим делом сами подумайте: как бы вы это сделали? Бывалым ценителям алгоритмических задачек этот вопрос даже каплю пота не вызовет, а вот средних работяг может заставить попотеть.
Первое, что надо понимать - если у вас просто есть готовый массив и ничего больше, то вы не найдете минимум за константное время. Нужно что-то думать. Второе, что надо понимать - очень часто можно довольно просто понизить сложность операции за счет применения правильного хранилища. Например, если превратить массив в неупорядоченное мультимножество, то сложность поиска понижается с О(n) до О(~1). И с этой мысли нужно начинать, когда от вас требуют понизить сложность. Задумайтесь. Не зря нам дали удобную обертку для массива. Мы можем контролировать, как именно будут добавляться и удаляться данные из него, а также можем хранить в этом классе какие-то дополнительные структуры.
В данном случае, решением будет добавить в класс поле current_min_stack - стек, в который будет хранить текущие минимумы для каждого элемента исходного массива. Как только вызывается push_back(value), в current_min_stack добавляется минимум из value и текущей верхушки current_min_stack. На каждый вызов pop_back мы убираем из current_min_stack его последний элемент. А на вызов min мы возвращаем последний элемент из стека текущего минимума.
Не самый эффективный способ, знаем и получше. Вот и напишите в комментах, как улучшить алгоритм.
Stay cool.
#algorithms
Идиома RAII
Наши подписчики присылают нам новые идеи и даже конспекты лекций! Поблагодарим Сергея Нефедова за это 🙂
В сообществе C++ разработчиков достаточно часто можно услышать об идиоме RAII (resource acquisition is initialization). Про нее уже было написано много и не раз, но и я попробую рассказать про нее по-своему.
Начну издалека, но так, чтобы вам было понятнее. Каждая вычислительная система обладает ресурсами - объектами вычислительной системы, которые ограничены в количестве и времени использования. Такой объект необходимо вернуть этой системе. Примером таких ресурсов могут стать оперативная память, доступ к файлу, сетевое соединение.
Каждый объект данного типа зачастую обладает обобщенным жизненным циклом:
1. Захват ресурса
Пример: выделение памяти, получение доступа, установка соединения.
2. Владение ресурсом
Пример: чтение/запись в память/файл, передача данных по соединению
3. Возврат ресурса
Пример: освобождение памяти, закрытие файла / соединения
Суть идиомы RAII заключается в том, чтобы данный жизненный цикл всегда был исполнен, т.е. ресурс всегда будет возвращен системе, когда он программе больше не нужен. Идиома предлагает вам создавать сущности в ваших программах таким образом, чтобы избежать утечки этих ограниченных ресурсов. Это может быть неочевидно в рамках учебных программ. Но в больших коммерческих проектах, которые могут эксплуатироваться годами без перерыва, отсутствие менеджмента ресурсов может привести к исчерпанию этих ресурсов и потере целевой функциональности. Срок годности вашего продукта будет ограничен временем исполнения или запасом этих ресурсов. Это уже не IT технологии...
Как же этого достичь? Обратите внимание на классы в C++, ведь они тоже соответствуют этому жизненному циклу. Давайте проведем аналогии:
1. Конструктор класса — захват ресурса
Начало жизни объекта: можно выделить память, открыть файл, установить соединение.
2. Ваши методы класса — владение ресурсом
Множество операций над ресурсом. Например, запись в память, файл или передача данных по соединению. Так же тут могут быть определены операции передачи владения ресурсом другому объекту. Данная тема будет подробнее раскрыта в следующих постах про move семантику.
3. Деструктор класса — возврат ресурса
Завершение жизни объекта: возвращаем системе захваченные ресурсы - освобождаем память, закрываем файлы и соединение.
Классы, созданные с подачи идиомы RAII, берут на себя ответственность менеджмента ресурсов и предоставления механизмов взаимодействия с ними.
Примечательно, что возврат ресурсов будет происходить во время уничтожения данного объекта - автоматически, при выходе из области видимости объекта этого класса. Это является очень удобным механизмом контроля за ресурсами для разработчика.
Примерами таких классов из стандартной библиотеки C++ являются std::string, а так же умные указатели:
1) std::unique_ptr<typename> - удаляет выделенный на куче объект типа typename при выходе из области видимости.
2) std::shared_ptr<typename> - удаляет выделенный на куче объект типа typename при выходе из области видимости всех копий данного умного указателя.
Если вам интересно узнать мнение авторов по конкретной теме, поделиться своими знаниями или задать интересный вопрос - пишите в комментах. Попробуем разобраться вместе.
#cppcore #goodpractice
Наши подписчики присылают нам новые идеи и даже конспекты лекций! Поблагодарим Сергея Нефедова за это 🙂
В сообществе C++ разработчиков достаточно часто можно услышать об идиоме RAII (resource acquisition is initialization). Про нее уже было написано много и не раз, но и я попробую рассказать про нее по-своему.
Начну издалека, но так, чтобы вам было понятнее. Каждая вычислительная система обладает ресурсами - объектами вычислительной системы, которые ограничены в количестве и времени использования. Такой объект необходимо вернуть этой системе. Примером таких ресурсов могут стать оперативная память, доступ к файлу, сетевое соединение.
Каждый объект данного типа зачастую обладает обобщенным жизненным циклом:
1. Захват ресурса
Пример: выделение памяти, получение доступа, установка соединения.
2. Владение ресурсом
Пример: чтение/запись в память/файл, передача данных по соединению
3. Возврат ресурса
Пример: освобождение памяти, закрытие файла / соединения
Суть идиомы RAII заключается в том, чтобы данный жизненный цикл всегда был исполнен, т.е. ресурс всегда будет возвращен системе, когда он программе больше не нужен. Идиома предлагает вам создавать сущности в ваших программах таким образом, чтобы избежать утечки этих ограниченных ресурсов. Это может быть неочевидно в рамках учебных программ. Но в больших коммерческих проектах, которые могут эксплуатироваться годами без перерыва, отсутствие менеджмента ресурсов может привести к исчерпанию этих ресурсов и потере целевой функциональности. Срок годности вашего продукта будет ограничен временем исполнения или запасом этих ресурсов. Это уже не IT технологии...
Как же этого достичь? Обратите внимание на классы в C++, ведь они тоже соответствуют этому жизненному циклу. Давайте проведем аналогии:
1. Конструктор класса — захват ресурса
Начало жизни объекта: можно выделить память, открыть файл, установить соединение.
2. Ваши методы класса — владение ресурсом
Множество операций над ресурсом. Например, запись в память, файл или передача данных по соединению. Так же тут могут быть определены операции передачи владения ресурсом другому объекту. Данная тема будет подробнее раскрыта в следующих постах про move семантику.
3. Деструктор класса — возврат ресурса
Завершение жизни объекта: возвращаем системе захваченные ресурсы - освобождаем память, закрываем файлы и соединение.
Классы, созданные с подачи идиомы RAII, берут на себя ответственность менеджмента ресурсов и предоставления механизмов взаимодействия с ними.
Примечательно, что возврат ресурсов будет происходить во время уничтожения данного объекта - автоматически, при выходе из области видимости объекта этого класса. Это является очень удобным механизмом контроля за ресурсами для разработчика.
Примерами таких классов из стандартной библиотеки C++ являются std::string, а так же умные указатели:
1) std::unique_ptr<typename> - удаляет выделенный на куче объект типа typename при выходе из области видимости.
2) std::shared_ptr<typename> - удаляет выделенный на куче объект типа typename при выходе из области видимости всех копий данного умного указателя.
Если вам интересно узнать мнение авторов по конкретной теме, поделиться своими знаниями или задать интересный вопрос - пишите в комментах. Попробуем разобраться вместе.
#cppcore #goodpractice
Почему РКН не сможет полностью заблокировать VPN в России Ч2
Первая часть тут. Причин на самом деле немного больше, чем я описывал ранее, и они гораздо более абстрактные.
Начнём, как всегда, с базы, а точнее с экономики. Большинство адекватных IT-компаний в России использую впн для доступа к корпоративным ресурсам (надо ещё учитывать, что сейчас даже самая облезлая собака считает себя ИТ-компанией). С учётом увеличивающегося хакерского давления на отрасль, использование корпоративного VPN - критическая необходимость для сохранения конфиденциальности информации и защиты сети от несанкционированного доступа. Также специалистам нужны многие сервисы, к которым в России доступ запрещён: линкедин для эйчаров, quora для прогеров и прочих работяг, перечислять можно много. Исходя из всего этого, чисто экономической точки зрения, представьте потери отрасли от блокировки впн трафика. Это десятки и сотни миллиардов рублей. И непозволительная роскошь для страны, которая сильно нуждается в отечественных ИТ-продуктах.
Помимо этого есть и главный идейный фактор. Люди на атаку любой сложности рано или поздно изобретают защиту для этой атаки. Естественная гонка вооружений. На любой интернетный запрет от нашего правительства найдётся лазейка. VPN был такой лазейкой в 18 году, когда РКН блокировал телеграмм. Дальше на помощь придут так называемые технологии обфускации трафика.
Цель обфускации - маскировка VPN трафика, делая его похожим на обычный трафик, чтобы обойти DPI. Например, данные можно скрыть под видом изображений или звуковых файлов. Обфусцирующие протоколы, типа Shadowsocks, прекрасно проявляют себя в Китае, как средство обхода их великого файервола.
Так что заявления РКН нужны просто, чтобы пыль в глаза бросить. Пока есть хоть один провод, соединяющий РФ с остальным интернетом, наши кулибины найдут способы для связи с внешним миром.
Stay calm. Stay cool.
#net #howitworks #fun
Первая часть тут. Причин на самом деле немного больше, чем я описывал ранее, и они гораздо более абстрактные.
Начнём, как всегда, с базы, а точнее с экономики. Большинство адекватных IT-компаний в России использую впн для доступа к корпоративным ресурсам (надо ещё учитывать, что сейчас даже самая облезлая собака считает себя ИТ-компанией). С учётом увеличивающегося хакерского давления на отрасль, использование корпоративного VPN - критическая необходимость для сохранения конфиденциальности информации и защиты сети от несанкционированного доступа. Также специалистам нужны многие сервисы, к которым в России доступ запрещён: линкедин для эйчаров, quora для прогеров и прочих работяг, перечислять можно много. Исходя из всего этого, чисто экономической точки зрения, представьте потери отрасли от блокировки впн трафика. Это десятки и сотни миллиардов рублей. И непозволительная роскошь для страны, которая сильно нуждается в отечественных ИТ-продуктах.
Помимо этого есть и главный идейный фактор. Люди на атаку любой сложности рано или поздно изобретают защиту для этой атаки. Естественная гонка вооружений. На любой интернетный запрет от нашего правительства найдётся лазейка. VPN был такой лазейкой в 18 году, когда РКН блокировал телеграмм. Дальше на помощь придут так называемые технологии обфускации трафика.
Цель обфускации - маскировка VPN трафика, делая его похожим на обычный трафик, чтобы обойти DPI. Например, данные можно скрыть под видом изображений или звуковых файлов. Обфусцирующие протоколы, типа Shadowsocks, прекрасно проявляют себя в Китае, как средство обхода их великого файервола.
Так что заявления РКН нужны просто, чтобы пыль в глаза бросить. Пока есть хоть один провод, соединяющий РФ с остальным интернетом, наши кулибины найдут способы для связи с внешним миром.
Stay calm. Stay cool.
#net #howitworks #fun
Предупреждения компилятора Ч1
Наверно многие попадали в такую ситуацию, когда ваша программа работала корректно, но компилятор заваливал вас предупреждениями о тех или иных потенциальных проблемах. Нередко в приоритет ставится в первую очередь возможность компиляции и запуска, а уже потом исправление предупреждений. Это зачастую приводит к привычке игнорировать их совсем...
Но давайте подумаем, зачем же они вообще выводятся? В конце концов, кто-то посчитал важным добавить в компиляторе эти сообщения. Неужели это сделано, чтобы подпортить вам и без того сложную жизнь C++ разработчика?
Конечно, нет. Скажу больше, предупреждения должны вам помогать. Тревожным сигналом является то, если они вам мешают! Забегая вперед, скажу сразу, что в достаточно крупных компаниях возникновение предупреждений считается недопустимым. Сборка проектов устроена так, что возникновение любого предупреждения приводит к ошибке компиляции. Всё строго.
Предупреждения указывают на потенциально опасное место, где может возникнуть ошибка. Возможно, что она возникает только в определенных сценариях, с определенным набором данных. Но можете ли вы гарантировать, что такие сценарии никогда не произойдут? А если можете, то зачем вам засорять вывод лишними предупреждениями?
Некоторые ошибки допускаются случайно, по невнимательности, даже несмотря на высокие компетенции разработчика. Если предупреждений будет много, как быстро он заметит свою ошибку? Отсутствие каких-либо предупреждений по умолчанию позволит однозначно обратить на это внимание, когда оно возникнет.
Исправляйте все предупреждения. Строгость и дисциплинированность будут играть вам на руку на поздних этапах разработки, до которых с другими подходами можно и вовсе не дотянуть.
#compiler #goodpractice
Наверно многие попадали в такую ситуацию, когда ваша программа работала корректно, но компилятор заваливал вас предупреждениями о тех или иных потенциальных проблемах. Нередко в приоритет ставится в первую очередь возможность компиляции и запуска, а уже потом исправление предупреждений. Это зачастую приводит к привычке игнорировать их совсем...
Но давайте подумаем, зачем же они вообще выводятся? В конце концов, кто-то посчитал важным добавить в компиляторе эти сообщения. Неужели это сделано, чтобы подпортить вам и без того сложную жизнь C++ разработчика?
Конечно, нет. Скажу больше, предупреждения должны вам помогать. Тревожным сигналом является то, если они вам мешают! Забегая вперед, скажу сразу, что в достаточно крупных компаниях возникновение предупреждений считается недопустимым. Сборка проектов устроена так, что возникновение любого предупреждения приводит к ошибке компиляции. Всё строго.
Предупреждения указывают на потенциально опасное место, где может возникнуть ошибка. Возможно, что она возникает только в определенных сценариях, с определенным набором данных. Но можете ли вы гарантировать, что такие сценарии никогда не произойдут? А если можете, то зачем вам засорять вывод лишними предупреждениями?
Некоторые ошибки допускаются случайно, по невнимательности, даже несмотря на высокие компетенции разработчика. Если предупреждений будет много, как быстро он заметит свою ошибку? Отсутствие каких-либо предупреждений по умолчанию позволит однозначно обратить на это внимание, когда оно возникнет.
Исправляйте все предупреждения. Строгость и дисциплинированность будут играть вам на руку на поздних этапах разработки, до которых с другими подходами можно и вовсе не дотянуть.
#compiler #goodpractice
Предупреждения компилятора Ч2
Продолжаем тему предупреждений компилятора.
У каждого предупреждения существует некоторый паттерн, который компилятор пытается распознать и сообщить об этом разработчику.
Активировать данную функциональность можно двумя опциями компилятора:
Они включают в себя очень большое количество проверок. Рассмотрим некоторые типичные примеры:
1. Неиспользуемые переменные - пример
Переменная была объявлена, но не используется в коде. Разработчик забыл её использовать, либо появился так называемый мертвый код.
2. Неинициализированные переменные - пример
Значение переменной читается и используется до того, как ей было присвоено начальное значение. Достаточно серьезная ошибка, классифицируемая как UB - undefined behavior - неопределенное поведение.
3. Недостижимый код - пример
В вашей программе появился участок кода, который никогда не будет выполнен. Это может быть вызвано неправильной логикой или условиями, которые всегда являются ложными или всегда истинными.
4. Неправильное использование типов данных - пример
Преобразовании типов данных, которые могут привести к потере данных или неожиданному поведению программы.
5. Неоднозначные операции - пример
Компилятор может предупредить о неоднозначном использовании операций или перегруженных операторов, когда он не может определить, какой именно вариант операции вы хотите использовать.
6. Потенциальные проблемы безопасности - пример
Компилятор может выдавать предупреждения о возможных проблемах безопасности, таких как использование небезопасных функций или операций, которые могут привести к переполнению буфера.
К сожалению, не все проблемы кода могут быть распознаны компилятором на этапе компиляции, т.е. без ее запуска. Так что развивайте внутри недр своего мозга виртуальный компилятор - это необходимый инструмент каждого разработчика.
#compiler #goodpractice
Продолжаем тему предупреждений компилятора.
У каждого предупреждения существует некоторый паттерн, который компилятор пытается распознать и сообщить об этом разработчику.
Активировать данную функциональность можно двумя опциями компилятора:
g++ source.cpp -Wall -Wextra
Они включают в себя очень большое количество проверок. Рассмотрим некоторые типичные примеры:
1. Неиспользуемые переменные - пример
Переменная была объявлена, но не используется в коде. Разработчик забыл её использовать, либо появился так называемый мертвый код.
2. Неинициализированные переменные - пример
Значение переменной читается и используется до того, как ей было присвоено начальное значение. Достаточно серьезная ошибка, классифицируемая как UB - undefined behavior - неопределенное поведение.
3. Недостижимый код - пример
В вашей программе появился участок кода, который никогда не будет выполнен. Это может быть вызвано неправильной логикой или условиями, которые всегда являются ложными или всегда истинными.
4. Неправильное использование типов данных - пример
Преобразовании типов данных, которые могут привести к потере данных или неожиданному поведению программы.
5. Неоднозначные операции - пример
Компилятор может предупредить о неоднозначном использовании операций или перегруженных операторов, когда он не может определить, какой именно вариант операции вы хотите использовать.
6. Потенциальные проблемы безопасности - пример
Компилятор может выдавать предупреждения о возможных проблемах безопасности, таких как использование небезопасных функций или операций, которые могут привести к переполнению буфера.
К сожалению, не все проблемы кода могут быть распознаны компилятором на этапе компиляции, т.е. без ее запуска. Так что развивайте внутри недр своего мозга виртуальный компилятор - это необходимый инструмент каждого разработчика.
#compiler #goodpractice
Предупреждения компилятора Ч3
Завершаем серию постов о предупреждениях компилятора. На этот раз поговорим об исправлениях предупреждений.
Самым логичным способом устранения предупреждения является влияние на причины возникновения, однако в моей практике встречался и другой способ - сокрытие предупреждения. Антипример: https://compiler-explorer.com/z/K9v8j6Kbc. Такой способ позволяет замаскировать проблему, которая никуда не исчезнет!
Более того, при неумелом использовании сокрытие предупреждений распространится и на остальные части вашего проекта. Уязвимости созданные в начале разработки, перекочуют в разные части проекта, скопируются, а так же обрастут новыми потенциальными уязвимостями или костылями. Все это будет незаметно наращивать технический долг.
Возможно, что однажды это будет раскрыто. И вот тут уже перед вами будет стоять дилемма: проигнорировать их опять или героически начать всё исправлять? Исправлять баги в действующем продукте - это не только долго, но и опасно. Как гласит университетская шутка: "чётное количество ошибок может давать правильный результат".
Этот инструмент должен был вам помогать с самого первого дня разработки проекта, а теперь не может полноценно функционировать вовсе. Вероятно, что лучше было бы вовсе отказаться от предупреждений компилятора, чем пытаться их замаскировать? Другим эта функция теперь недоступна.
В качестве заключения, я хочу донести до вас одну простую мысль. Исправление предупреждений, однозначно, должно быть качественным, а не фиктивным.
#compiler #goodpractice
Завершаем серию постов о предупреждениях компилятора. На этот раз поговорим об исправлениях предупреждений.
Самым логичным способом устранения предупреждения является влияние на причины возникновения, однако в моей практике встречался и другой способ - сокрытие предупреждения. Антипример: https://compiler-explorer.com/z/K9v8j6Kbc. Такой способ позволяет замаскировать проблему, которая никуда не исчезнет!
Более того, при неумелом использовании сокрытие предупреждений распространится и на остальные части вашего проекта. Уязвимости созданные в начале разработки, перекочуют в разные части проекта, скопируются, а так же обрастут новыми потенциальными уязвимостями или костылями. Все это будет незаметно наращивать технический долг.
Возможно, что однажды это будет раскрыто. И вот тут уже перед вами будет стоять дилемма: проигнорировать их опять или героически начать всё исправлять? Исправлять баги в действующем продукте - это не только долго, но и опасно. Как гласит университетская шутка: "чётное количество ошибок может давать правильный результат".
Этот инструмент должен был вам помогать с самого первого дня разработки проекта, а теперь не может полноценно функционировать вовсе. Вероятно, что лучше было бы вовсе отказаться от предупреждений компилятора, чем пытаться их замаскировать? Другим эта функция теперь недоступна.
В качестве заключения, я хочу донести до вас одну простую мысль. Исправление предупреждений, однозначно, должно быть качественным, а не фиктивным.
#compiler #goodpractice
Код ревью Ч1
Во многих IT-компаниях процедура код-ревью является обязательной. Перед добавлением новых изменений в кодовую базу команда разработчиков проверяет друг друга с целью улучшить решение. Побочным эффектом является повышение компетенций автора. Я предлагаю рассматривать эту практику именно с этой стороны и сейчас поговорим, почему именно так.
Нередко на просторах интернета можно встретить достаточно токсичные треды с обсуждением какого-либо решения... Неудивительно, что многие начинающие разработчики побаиваются код-ревью. И все же это интернет, а не реальные коллеги. Тем не менее это вызов вашим компетенциям. Рассматривайте это, как способ проверить и повысить ваши навыки коммуникации и убеждения, расширить знания программирования. Часто нет однозначно правильного решения, все варианты обладают своими плюсами и минусами. В таком случае приходится убеждать других - это все влияет на ваш профессиональный авторитет в коллективе. Это важно и для собственной самооценки, и для карьерного роста.
Эмоциональная нагрузка со временем будет уменьшаться, и даже наоборот перерасти в азарт: а слабо ли мне пройти ревью без единого исправления? Это челлендж самому себе, но его проходить реально прикольно. Желаю вам испытать это чувство собственной неотразимости! 😊
К сожалению, в некоторых компаниях пренебрегают такой процедурой. На моей практике был печальный опыт получения проекта от другой компании, где не было такой процедуры. Отсутствие проверки расхолаживает разработчиков, а качество кода не улучшается со временем. Всегда есть возможность сэкономить и запилить костыль до лучших времен, которые, возможно, никогда не настанут. Я не преувеличиваю, бывает очень сложно доказать вышестоящему руководству, что необходимо выделить время на рефакторинг. С точки зрения потребителя и бизнеса ничего не меняется, это внутренние дела вашей кухни.
Я сделал однозначный для себя вывод, что код-ревью является необходимой процедурой коммерческой разработки. Качественный код нужно публиковать как можно раньше. Да, это требует времени и сил, но развивает коллектив и позволяет поддерживать проект очень долгое время, сокращает количество ошибок и багов, упрощает жизнь программистов в долгосрочной перспективе.
В следующей части давайте поговорим, как проводить код-ревью для своих коллег. На мой взгляд есть важные моменты, я хочу быть уверен, что вы их точно будете знать.
#commercial #goodpractice
Во многих IT-компаниях процедура код-ревью является обязательной. Перед добавлением новых изменений в кодовую базу команда разработчиков проверяет друг друга с целью улучшить решение. Побочным эффектом является повышение компетенций автора. Я предлагаю рассматривать эту практику именно с этой стороны и сейчас поговорим, почему именно так.
Нередко на просторах интернета можно встретить достаточно токсичные треды с обсуждением какого-либо решения... Неудивительно, что многие начинающие разработчики побаиваются код-ревью. И все же это интернет, а не реальные коллеги. Тем не менее это вызов вашим компетенциям. Рассматривайте это, как способ проверить и повысить ваши навыки коммуникации и убеждения, расширить знания программирования. Часто нет однозначно правильного решения, все варианты обладают своими плюсами и минусами. В таком случае приходится убеждать других - это все влияет на ваш профессиональный авторитет в коллективе. Это важно и для собственной самооценки, и для карьерного роста.
Эмоциональная нагрузка со временем будет уменьшаться, и даже наоборот перерасти в азарт: а слабо ли мне пройти ревью без единого исправления? Это челлендж самому себе, но его проходить реально прикольно. Желаю вам испытать это чувство собственной неотразимости! 😊
К сожалению, в некоторых компаниях пренебрегают такой процедурой. На моей практике был печальный опыт получения проекта от другой компании, где не было такой процедуры. Отсутствие проверки расхолаживает разработчиков, а качество кода не улучшается со временем. Всегда есть возможность сэкономить и запилить костыль до лучших времен, которые, возможно, никогда не настанут. Я не преувеличиваю, бывает очень сложно доказать вышестоящему руководству, что необходимо выделить время на рефакторинг. С точки зрения потребителя и бизнеса ничего не меняется, это внутренние дела вашей кухни.
Я сделал однозначный для себя вывод, что код-ревью является необходимой процедурой коммерческой разработки. Качественный код нужно публиковать как можно раньше. Да, это требует времени и сил, но развивает коллектив и позволяет поддерживать проект очень долгое время, сокращает количество ошибок и багов, упрощает жизнь программистов в долгосрочной перспективе.
В следующей части давайте поговорим, как проводить код-ревью для своих коллег. На мой взгляд есть важные моменты, я хочу быть уверен, что вы их точно будете знать.
#commercial #goodpractice
Код ревью Ч2
Продолжаем тему код-ревью. На этот раз поговорим об этом с точки зрения проверяющего.
Эмоциональный фон сильно влияет на скорость и качество исправлений замечаний, исполнения пожеланий других разработчиков. В одном из исследований было замечено, что люди получают большее удовольствие от работы, если в компании работает лучший друг. Зачем упускать возможность получать от работы удовольствие? Мы решаем проблему, а не самоутверждаемся за счет других!
На моем текущем месте работы однажды проводили тренинг по командообразованию. Мы обсуждали разные сложности в работе, в том числе эмоциональную нагрузку на код-ревью. Нам понадобилось два полных рабочих дня, чтобы проработать все сложности... И знаете какой итог был по окончанию этого мероприятия? Не поверите, но мы договорились делать друг другу комплименты там, где нам действительно что-то понравилось 😄 Конечно, лишь немногие стали придерживаться этой рекомендации, но прошел уже почти год и мне действительно стало приятнее работать с этими единицами!
Заводите друзей среди коллег, либо общайтесь, как с другом, пока вы ими действительно не станете. Это определяет всю манеру и способы взаимодействия друг с другом! Например:
1. Терпеливость и внимательность в общении
2. Допущение вкусовых предпочтений автора кода
Конечно, это не отменяет конструктивной критики, а лишь корректирует форму преподнесения ваших замечаний. Главной целью, как я уже говорил ранее, является улучшение предоставленного решения: исправление ошибок, работа над читаемостью кода, проверка логики и т.д.
«Абсолютная корректность системы - недостижима. Каждая последняя найденная ошибка является предпоследней»
Этот закон отражает сложность нетривиальных систем, с которыми разработчики сталкиваются каждый день. Следовательно, и решение необходимо искать ошибки на самых разных уровнях. Начиная с самых простых - синтаксических, заканчивая самыми сложными - соответствия целям бизнеса. В идеале, целеустремленный разработчик будет стремиться к пониманию потребностей своих клиентов и работать над их достижением.
Вовлеченность в каждый из этих вопросов определяет профессионализм разработчика.
#commercial #goodpractice
Продолжаем тему код-ревью. На этот раз поговорим об этом с точки зрения проверяющего.
Эмоциональный фон сильно влияет на скорость и качество исправлений замечаний, исполнения пожеланий других разработчиков. В одном из исследований было замечено, что люди получают большее удовольствие от работы, если в компании работает лучший друг. Зачем упускать возможность получать от работы удовольствие? Мы решаем проблему, а не самоутверждаемся за счет других!
На моем текущем месте работы однажды проводили тренинг по командообразованию. Мы обсуждали разные сложности в работе, в том числе эмоциональную нагрузку на код-ревью. Нам понадобилось два полных рабочих дня, чтобы проработать все сложности... И знаете какой итог был по окончанию этого мероприятия? Не поверите, но мы договорились делать друг другу комплименты там, где нам действительно что-то понравилось 😄 Конечно, лишь немногие стали придерживаться этой рекомендации, но прошел уже почти год и мне действительно стало приятнее работать с этими единицами!
Заводите друзей среди коллег, либо общайтесь, как с другом, пока вы ими действительно не станете. Это определяет всю манеру и способы взаимодействия друг с другом! Например:
1. Терпеливость и внимательность в общении
2. Допущение вкусовых предпочтений автора кода
Конечно, это не отменяет конструктивной критики, а лишь корректирует форму преподнесения ваших замечаний. Главной целью, как я уже говорил ранее, является улучшение предоставленного решения: исправление ошибок, работа над читаемостью кода, проверка логики и т.д.
«Абсолютная корректность системы - недостижима. Каждая последняя найденная ошибка является предпоследней»
Этот закон отражает сложность нетривиальных систем, с которыми разработчики сталкиваются каждый день. Следовательно, и решение необходимо искать ошибки на самых разных уровнях. Начиная с самых простых - синтаксических, заканчивая самыми сложными - соответствия целям бизнеса. В идеале, целеустремленный разработчик будет стремиться к пониманию потребностей своих клиентов и работать над их достижением.
Вовлеченность в каждый из этих вопросов определяет профессионализм разработчика.
#commercial #goodpractice
Код ревью Ч3
Продолжаем тему код-ревью. Давайте теперь попробуем назвать общее требование к автору и проверяющим. Я бы выделил коллективную ответственность за эволюцию кода проекта.
Специально использую термин эволюция, т.к. ваш проект изменяется каждый день. Заказчики присылают новые требования, конкуренты развивают свои продукты, разработчики добавляют новые фишки и исправляют баги... Каждая строчка кода проходит естественный и искусственный отбор.
Выбор потребителей - это этапы естественного отбора. Если ваш продукт уступает в чем-то, то его перестают выбирать. При такой динамике количество клиентов будет недостаточно, чтобы поддерживать ПО дальше, т.е. произойдет отбор не в вашу пользу.
Ваши замечания на ревью - это этап искусственного отбора. Проверка кода - это капля в море, но именно из них и состоит весь проект. Те баги, что вы пропускаете вперед, вы же и будете исправлять! Да, иногда стоит поискать слова и аргументы, чтобы объяснить сложную суть проблемы или преимущество вашего решения, которое вы видите сейчас или в будущем.
С моей точки зрения, автор несёт большую ответственность, т.к. именно он является наиболее погруженным в контекст участником, выполняет первичное тестирование. Если смотреть с этой позиции, автор выступает еще и в роли проверяющего, а исходное решение является нулевой итерацией проверки. Ну, и следовательно, их надо найти и устранить 😃
Предполагается, что опубликованное решение работает корректно, но мы всегда должны мыслить критически - проверять это предположение и превращать его в утверждение.
Пишите вопросы! Вам ответ - нам контент!
#commercial #goodpractice
Продолжаем тему код-ревью. Давайте теперь попробуем назвать общее требование к автору и проверяющим. Я бы выделил коллективную ответственность за эволюцию кода проекта.
Специально использую термин эволюция, т.к. ваш проект изменяется каждый день. Заказчики присылают новые требования, конкуренты развивают свои продукты, разработчики добавляют новые фишки и исправляют баги... Каждая строчка кода проходит естественный и искусственный отбор.
Выбор потребителей - это этапы естественного отбора. Если ваш продукт уступает в чем-то, то его перестают выбирать. При такой динамике количество клиентов будет недостаточно, чтобы поддерживать ПО дальше, т.е. произойдет отбор не в вашу пользу.
Ваши замечания на ревью - это этап искусственного отбора. Проверка кода - это капля в море, но именно из них и состоит весь проект. Те баги, что вы пропускаете вперед, вы же и будете исправлять! Да, иногда стоит поискать слова и аргументы, чтобы объяснить сложную суть проблемы или преимущество вашего решения, которое вы видите сейчас или в будущем.
С моей точки зрения, автор несёт большую ответственность, т.к. именно он является наиболее погруженным в контекст участником, выполняет первичное тестирование. Если смотреть с этой позиции, автор выступает еще и в роли проверяющего, а исходное решение является нулевой итерацией проверки. Ну, и следовательно, их надо найти и устранить 😃
Предполагается, что опубликованное решение работает корректно, но мы всегда должны мыслить критически - проверять это предположение и превращать его в утверждение.
Пишите вопросы! Вам ответ - нам контент!
#commercial #goodpractice
Код ревью Ч4
Продолжаем тему код-ревью.
Конечно, найти все ошибки самостоятельно нельзя. Это следует из закона, который я упомянул в одном из предыдущих постов. Но можно поспособствовать себе в этом! В основном, большая часть ошибок допускается по невнимательности или неосознанности принятых решений. Мне помогают следующие рекомендации:
1. Отдыхать после работы.
Без комментариев.
2. Не публиковать код вечером.
Не думаю, что вашим коллегам будет принципиально важно увидеть код как можно скорее - вечером или утром следующего дня. А вот вам этот перерыв поможет перевести дух и взглянуть на решение свежим взглядом. Я так неоднократно находил у себя разные мелочи.
3. Спрашивайте себя почему и зачем?
Почаще спрашивайте себя, зачем вы приняли такой решение тут? Почему это работает так там? И не забывайте отвечать на эти вопросы :) Это просто влияет на вашу осознанность и погруженность в код.
3. Представить себя на месте коллег
Бывало ли у вас такое, что вы идете по очень знакомой улице, но вспоминаете свои ощущения и впечатления, как это было впервые? Попробуйте погрузиться в это ментальное состояние и попробовать посмотреть на свой код чужими глазами, абстрагировавшись от контекста. Это то, что увидят ваши коллеги. Поймут ли они то, что задумали вы?
4. Как бы сказал ваш авторитет?
Да, можно представить себя на месте вашего авторитета и подумать, что бы он сказал по поводу того или иного решения?
Надеюсь, что и для вас эти рекомендации будут полезны! 😉
#commercial #goodpractice
Продолжаем тему код-ревью.
Конечно, найти все ошибки самостоятельно нельзя. Это следует из закона, который я упомянул в одном из предыдущих постов. Но можно поспособствовать себе в этом! В основном, большая часть ошибок допускается по невнимательности или неосознанности принятых решений. Мне помогают следующие рекомендации:
1. Отдыхать после работы.
Без комментариев.
2. Не публиковать код вечером.
Не думаю, что вашим коллегам будет принципиально важно увидеть код как можно скорее - вечером или утром следующего дня. А вот вам этот перерыв поможет перевести дух и взглянуть на решение свежим взглядом. Я так неоднократно находил у себя разные мелочи.
3. Спрашивайте себя почему и зачем?
Почаще спрашивайте себя, зачем вы приняли такой решение тут? Почему это работает так там? И не забывайте отвечать на эти вопросы :) Это просто влияет на вашу осознанность и погруженность в код.
3. Представить себя на месте коллег
Бывало ли у вас такое, что вы идете по очень знакомой улице, но вспоминаете свои ощущения и впечатления, как это было впервые? Попробуйте погрузиться в это ментальное состояние и попробовать посмотреть на свой код чужими глазами, абстрагировавшись от контекста. Это то, что увидят ваши коллеги. Поймут ли они то, что задумали вы?
4. Как бы сказал ваш авторитет?
Да, можно представить себя на месте вашего авторитета и подумать, что бы он сказал по поводу того или иного решения?
Надеюсь, что и для вас эти рекомендации будут полезны! 😉
#commercial #goodpractice
std::optional
Сталкивались ли с необходимостью определить особенное состояние переменной, которая может принимать разные значения?
Приведу пример из практики. Недавно мне нужно было придумать интерфейс для передачи необязательных целочисленных параметров в функцию. В данном контексте особенным состоянием является отсутствие одного или нескольких значений.
Как вообще можно теоретические решить подобную проблему? Мне пришли в голову следующие варианты:
1. Определить несколько реализаций функций
Крайне неудобный способ, необходимо проверять всевозможные комбинации переменных и под них писать реализации функций.
2. Использовать специальную константу
Специально заданное значение может быть одним из возможных значений, которые передал пользователь, хоть и крайне редким. А вам, как коллегам автора решения, было бы удобно искать специально подобранные константы для ваших новых реализаций?
3. Передавать std::map с параметрами
Неудобное обращение и проверка существования ключа мапы. Накладные расходы на хранение и обслуживание структуры.
4. Передавать указатели на значения
Такой способ может показаться удобным, т.к. nullptr однозначно определяет отсутствие значения. Неудобно, что можно случайно забыть сделать проверку и словить segfault 🥲
5. Использовать специальный класс
Именно этот способ я и выбрал для себя. Давайте расскажу подробнее ниже.
В стандарте языка существует шаблонный класс std::optional<T>, который предоставляет разработчику возможность зарезервировать память под объект класса T и отложить момент инициализации, вплоть до бесконечности. Пользователь имеет возможность узнать состояние объекта и получить к нему доступ. В случае попытки получить доступ к объекту в невалидном состоянии будет брошено исключение std::bad_optional_access (лучше, чем не отлавливаемый segfault 😉).
По сути этот класс представляет из себя обертку над внутри лежащим классом, который добавляет специальное логическое поле для сохранения состояния объекта - инициализировано или нет. Объект std::optional резервирует память под объект класса T там, где его аллоцировали - на стеке или куче, как std::array. Это совсем немного увеличивает расход памяти (+1 байт) и выравнивает полученную структуру.
Это, действительно, удобный, явный и понятный способ показать, что значение переменной может отсутствовать. Помимо этого, класс так же снабжен специальными методами, которые позволяют обработать или изменять состояние внутрилежащего объекта. А при необходимости - обработать ошибочное поведение вашей программы.
Пример: https://compiler-explorer.com/z/65KrTnjbe
Так же это подталкивает меня к мысли, что можно создавать аналогичные обертки, которые будут хранить в себе намного больше состояний внутрилежащего объекта. Данные будут хорошо связаны и легки в использовании другими разработчиками
#cpp17 #STL
Сталкивались ли с необходимостью определить особенное состояние переменной, которая может принимать разные значения?
Приведу пример из практики. Недавно мне нужно было придумать интерфейс для передачи необязательных целочисленных параметров в функцию. В данном контексте особенным состоянием является отсутствие одного или нескольких значений.
Как вообще можно теоретические решить подобную проблему? Мне пришли в голову следующие варианты:
1. Определить несколько реализаций функций
Крайне неудобный способ, необходимо проверять всевозможные комбинации переменных и под них писать реализации функций.
2. Использовать специальную константу
Специально заданное значение может быть одним из возможных значений, которые передал пользователь, хоть и крайне редким. А вам, как коллегам автора решения, было бы удобно искать специально подобранные константы для ваших новых реализаций?
3. Передавать std::map с параметрами
Неудобное обращение и проверка существования ключа мапы. Накладные расходы на хранение и обслуживание структуры.
4. Передавать указатели на значения
Такой способ может показаться удобным, т.к. nullptr однозначно определяет отсутствие значения. Неудобно, что можно случайно забыть сделать проверку и словить segfault 🥲
5. Использовать специальный класс
Именно этот способ я и выбрал для себя. Давайте расскажу подробнее ниже.
В стандарте языка существует шаблонный класс std::optional<T>, который предоставляет разработчику возможность зарезервировать память под объект класса T и отложить момент инициализации, вплоть до бесконечности. Пользователь имеет возможность узнать состояние объекта и получить к нему доступ. В случае попытки получить доступ к объекту в невалидном состоянии будет брошено исключение std::bad_optional_access (лучше, чем не отлавливаемый segfault 😉).
По сути этот класс представляет из себя обертку над внутри лежащим классом, который добавляет специальное логическое поле для сохранения состояния объекта - инициализировано или нет. Объект std::optional резервирует память под объект класса T там, где его аллоцировали - на стеке или куче, как std::array. Это совсем немного увеличивает расход памяти (+1 байт) и выравнивает полученную структуру.
Это, действительно, удобный, явный и понятный способ показать, что значение переменной может отсутствовать. Помимо этого, класс так же снабжен специальными методами, которые позволяют обработать или изменять состояние внутрилежащего объекта. А при необходимости - обработать ошибочное поведение вашей программы.
Пример: https://compiler-explorer.com/z/65KrTnjbe
Так же это подталкивает меня к мысли, что можно создавать аналогичные обертки, которые будут хранить в себе намного больше состояний внутрилежащего объекта. Данные будут хорошо связаны и легки в использовании другими разработчиками
#cpp17 #STL
Как правильно запретить объекту копироваться
Бывают такие ситуации, когда объект владеет каким-то ресурсом единолично, то есть имеет над ним так называемый ownership. В этом случае время жизни и использования ресурса совпадает с временем жизни объекта, а использование этого же самого ресурса другим объектом будет приносить странные side effect’ы. Например у нас есть класс, который содержит файл в качестве поля и который открывает этот файл в конструкторе и закрывает в деструкторе. Что должно происходить при копировании объекта этого класса? Должен ли файл ещё раз открываться или в начале в копируемом объекте закрываться, а потом открываться в скопированном? Все варианты выглядят выстрелом в хлебало, поэтому логично запретить таким объектам копироваться.
Как это сделать? Самое широкоиспользуемое решение - объявить конструктор копирования и оператор присваивания приватными и жить счастливо. Никто снаружи класса не сможет получить доступ к этим специальным методам, а при попытке будет ошибка компиляции. Но это только снаружи класса. Внутри класса-то можно использовать приватные поля и методы. И тут уже огромный простор для ошибок, в зависимости от того, как вы определили специальные методы.
Такой код
class NonCopyable {
private:
NonCopyable( const NonCopyable& ) {}
void NonCopyable::operator=( const NonCopyable& ) {}
};
void NonCopyable::SomeOtherMehod() {
callSomething( *this );
}
Приведёт к вызову копирующего конструктора и копированию всех полей, что ведёт к неопределенному поведению.
Но С++11 подарил нам прекрасную фича - можно пометить любую функцию как = delete и компилятор просто не будет генерировать код для неё. Поэтому такие функции физически не могут быть вызваны. Пометив наши методы удаленными, мы уберём возможность любой сущности их вызывать и сильно обезопасим код.
К тому же, использование этой фичи повышает читаемость кода. А так как пометить удаленной можно любую функцию, то можно удалить ненужную перегрузку или запретить вызывать функцию с ошибочным приведением параметров.
Modern C++ - сила.
Stay cool.
#cppcore #cpp11 #goodpractice
Бывают такие ситуации, когда объект владеет каким-то ресурсом единолично, то есть имеет над ним так называемый ownership. В этом случае время жизни и использования ресурса совпадает с временем жизни объекта, а использование этого же самого ресурса другим объектом будет приносить странные side effect’ы. Например у нас есть класс, который содержит файл в качестве поля и который открывает этот файл в конструкторе и закрывает в деструкторе. Что должно происходить при копировании объекта этого класса? Должен ли файл ещё раз открываться или в начале в копируемом объекте закрываться, а потом открываться в скопированном? Все варианты выглядят выстрелом в хлебало, поэтому логично запретить таким объектам копироваться.
Как это сделать? Самое широкоиспользуемое решение - объявить конструктор копирования и оператор присваивания приватными и жить счастливо. Никто снаружи класса не сможет получить доступ к этим специальным методам, а при попытке будет ошибка компиляции. Но это только снаружи класса. Внутри класса-то можно использовать приватные поля и методы. И тут уже огромный простор для ошибок, в зависимости от того, как вы определили специальные методы.
Такой код
class NonCopyable {
private:
NonCopyable( const NonCopyable& ) {}
void NonCopyable::operator=( const NonCopyable& ) {}
};
void NonCopyable::SomeOtherMehod() {
callSomething( *this );
}
Приведёт к вызову копирующего конструктора и копированию всех полей, что ведёт к неопределенному поведению.
Но С++11 подарил нам прекрасную фича - можно пометить любую функцию как = delete и компилятор просто не будет генерировать код для неё. Поэтому такие функции физически не могут быть вызваны. Пометив наши методы удаленными, мы уберём возможность любой сущности их вызывать и сильно обезопасим код.
К тому же, использование этой фичи повышает читаемость кода. А так как пометить удаленной можно любую функцию, то можно удалить ненужную перегрузку или запретить вызывать функцию с ошибочным приведением параметров.
Modern C++ - сила.
Stay cool.
#cppcore #cpp11 #goodpractice
Предсказуемость
Этот пост я бы хотел посвятить теме предсказуемости в разработке программ. Это очень интересное свойство, корни которого заложены глубоко в человеке.
На одной из экскурсий по архитектуре моего города экскурсовод обратил внимание на восприятие человеком красоты архитектуры. Оно складывается не только из чисто субъективного ощущения прекрасного, но и визуального ощущения надежности строения. Иначе говоря, будете ли вы считать здание красивым, если ваше внутреннее чувство говорит о его хрупкости? Человек желает видеть перед собой прочное, монументальное здание. То есть поведение этого здания предсказуемо - оно не обрушится, там безопасно. Это является одним из факторов, определяющим дизайн здания и выбор отделочных материалов. Предсказуемость затрагивает многие сферы жизни. Строительство, отношения людей и т.д., в том числе программирование.
Предсказуемость поведения тех или иных участков кода позволяет нам с уверенностью взять их в работу и без сомнений доверить новые вычисления. Иначе это будет порождать в нас тревогу, а ни не рухнет ли там ничего? А правильно ли мы используем тот или иной инструмент для решения конкретных задач? Да, конечно, лопатой можно забивать гвозди, но эффективно ли это? Лопата быстро сломается, а вот молотком работать и удобнее, и быстрее.
Давайте вспомним предыдущие посты: магические числа, идиома RAII, комментарии в коде, Ссылки vs Указатели, optional, как правильно запретить копирование - не об этой ли предсказуемости шла речь в данных постах? Мы там всегда пытаемся что-то гарантировать, что-то показать, что-то сделать явным и однозначным.
Написание предсказуемого кода — это козырь, который позволит вам уверенней принимать решения, быстрее решать проблемы и легче ориентироваться в коде коллег. Все это связано с ограниченностью человеческих способностей, которую надо уметь принимать и работать с ней.
Конечно, может показаться скучным делать что-то предсказуемое. Но, уверяю, весь азарт и веселье как раз и заключается в том, чтобы обеспечить эту предсказуемость 😉
#howitworks #fun
Этот пост я бы хотел посвятить теме предсказуемости в разработке программ. Это очень интересное свойство, корни которого заложены глубоко в человеке.
На одной из экскурсий по архитектуре моего города экскурсовод обратил внимание на восприятие человеком красоты архитектуры. Оно складывается не только из чисто субъективного ощущения прекрасного, но и визуального ощущения надежности строения. Иначе говоря, будете ли вы считать здание красивым, если ваше внутреннее чувство говорит о его хрупкости? Человек желает видеть перед собой прочное, монументальное здание. То есть поведение этого здания предсказуемо - оно не обрушится, там безопасно. Это является одним из факторов, определяющим дизайн здания и выбор отделочных материалов. Предсказуемость затрагивает многие сферы жизни. Строительство, отношения людей и т.д., в том числе программирование.
Предсказуемость поведения тех или иных участков кода позволяет нам с уверенностью взять их в работу и без сомнений доверить новые вычисления. Иначе это будет порождать в нас тревогу, а ни не рухнет ли там ничего? А правильно ли мы используем тот или иной инструмент для решения конкретных задач? Да, конечно, лопатой можно забивать гвозди, но эффективно ли это? Лопата быстро сломается, а вот молотком работать и удобнее, и быстрее.
Давайте вспомним предыдущие посты: магические числа, идиома RAII, комментарии в коде, Ссылки vs Указатели, optional, как правильно запретить копирование - не об этой ли предсказуемости шла речь в данных постах? Мы там всегда пытаемся что-то гарантировать, что-то показать, что-то сделать явным и однозначным.
Написание предсказуемого кода — это козырь, который позволит вам уверенней принимать решения, быстрее решать проблемы и легче ориентироваться в коде коллег. Все это связано с ограниченностью человеческих способностей, которую надо уметь принимать и работать с ней.
Конечно, может показаться скучным делать что-то предсказуемое. Но, уверяю, весь азарт и веселье как раз и заключается в том, чтобы обеспечить эту предсказуемость 😉
#howitworks #fun
CREATE TABLE IF NOT EXIST
Вроде бы SQL запрос с подобным началом выглядит довольно разумно: зачем создавать таблицу, когда она уже есть в базе. Однако же он таит в себе проблему. В многопоточном приложении этот запрос может приводить к состоянию гонки.
Короткая справка по race condition'у: это такое состояние программы, когда корректность выполнения куска кода зависит от таймингов или порядка выполнения операций в нем.
Давайте посмотрим, что происходит внутри базы данных, что может вызвать такие проблемы. Запрос «CREATE TABLE IF NOT EXISTS» проверяет, существует ли таблица в базе данных. Если это не так, таблица создается; в противном случае он пропускает этап создания. Однако при одновременном выполнении нескольких запросов может возникнуть состояние гонки из-за следующих шагов:
1️⃣ Проверка существования таблицы. Первый шаг — проверить, существует ли таблица в базе данных. Это включает в себя доступ к метаданным базы данных и поиск имени таблицы.
2️⃣ Создание таблицы: если таблица не существует, запрос переходит к ее созданию. Это включает в себя выделение ресурсов, определение схемы таблицы и обновление метаданных.
2️⃣ Атомарность. Основная проблема возникает, когда несколько запросов одновременно проверяют существование таблицы. Они могут увидеть, что таблицы не существует, и создать ее самостоятельно. Результатом является попытка создание нескольких одинаковых таблиц, что нарушает смысл условия «ЕСЛИ НЕ СУЩЕСТВУЕТ».
Последствия этой проблемы: ошибки там, где вы их не ожидаете. Нельзя в большинстве реляционных баз данных взять и просто создать 2 одинаковые таблицы. Вы получите ошибку. А самое приятное, что эта ошибка зависит от Бога Рандома, поэтому отловить ее, воспроизвести или пофиксить будет непросто.
В будущем расскажу, как предотвратить эти проблемы.
Stay cool.
#database #multitasking
Вроде бы SQL запрос с подобным началом выглядит довольно разумно: зачем создавать таблицу, когда она уже есть в базе. Однако же он таит в себе проблему. В многопоточном приложении этот запрос может приводить к состоянию гонки.
Короткая справка по race condition'у: это такое состояние программы, когда корректность выполнения куска кода зависит от таймингов или порядка выполнения операций в нем.
Давайте посмотрим, что происходит внутри базы данных, что может вызвать такие проблемы. Запрос «CREATE TABLE IF NOT EXISTS» проверяет, существует ли таблица в базе данных. Если это не так, таблица создается; в противном случае он пропускает этап создания. Однако при одновременном выполнении нескольких запросов может возникнуть состояние гонки из-за следующих шагов:
1️⃣ Проверка существования таблицы. Первый шаг — проверить, существует ли таблица в базе данных. Это включает в себя доступ к метаданным базы данных и поиск имени таблицы.
2️⃣ Создание таблицы: если таблица не существует, запрос переходит к ее созданию. Это включает в себя выделение ресурсов, определение схемы таблицы и обновление метаданных.
2️⃣ Атомарность. Основная проблема возникает, когда несколько запросов одновременно проверяют существование таблицы. Они могут увидеть, что таблицы не существует, и создать ее самостоятельно. Результатом является попытка создание нескольких одинаковых таблиц, что нарушает смысл условия «ЕСЛИ НЕ СУЩЕСТВУЕТ».
Последствия этой проблемы: ошибки там, где вы их не ожидаете. Нельзя в большинстве реляционных баз данных взять и просто создать 2 одинаковые таблицы. Вы получите ошибку. А самое приятное, что эта ошибка зависит от Бога Рандома, поэтому отловить ее, воспроизвести или пофиксить будет непросто.
В будущем расскажу, как предотвратить эти проблемы.
Stay cool.
#database #multitasking