Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🤝32🔥18❤5👍1
В этой статье:
• Понять, как RAII помогает строить архитектуру и инварианты• Увидеть примеры управления разными ресурсами (не только памятью)• Научиться делать код устойчивым к исключениям и “утечкам ответственности”🔊 Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍6❤5😁1
Безопасный “побитовый каст” без UB — std::bit_cast!
Иногда нужно посмотреть на биты значения: получить IEEE754-представление
Многие делают это через
Сначала типичный “ручной” (опасный) вариант:
Теперь то же самое правильно одной строкой:
Частый практический кейс — быстро проверить знак/экспоненту
Ещё один популярный пример — получить “сырой”
И обратная операция — восстановить значение из битов (тоже безопасно, если размер совпадает):
🔥
📣 C++ Ready | #практика
Иногда нужно посмотреть на биты значения: получить IEEE754-представление
float, собрать/разобрать сетевой пакет, сделать хеш по битам, интерпретировать double как uint64_t.Многие делают это через
reinterpret_cast или “указательную магию” — и ловят UB из-за strict aliasing и выравнивания.Сначала типичный “ручной” (опасный) вариант:
float f = 1.0f;
auto u = *reinterpret_cast<std::uint32_t*>(&f); // UB!
use(u);
Теперь то же самое правильно одной строкой:
std::bit_cast (C++20) копирует биты между типами одинакового размера:float f = 1.0f;
auto u = std::bit_cast<std::uint32_t>(f);
use(u);
Частый практический кейс — быстро проверить знак/экспоненту
float без математики:bool is_negative(float x) {
auto bits = std::bit_cast<std::uint32_t>(x);
return (bits >> 31) != 0;
}Ещё один популярный пример — получить “сырой”
uint64_t из double для стабильного сравнения/логирования битов:std::uint64_t raw_bits(double d) {
return std::bit_cast<std::uint64_t>(d);
}И обратная операция — восстановить значение из битов (тоже безопасно, если размер совпадает):
double from_bits(std::uint64_t bits) {
return std::bit_cast<double>(bits);
}std::bit_cast делает намерение явным: “мне нужны биты, а не преобразование значения” — без reinterpret_cast, без строгого алиасинга, меньше шансов поймать UB.Please open Telegram to view this post
VIEW IN TELEGRAM
❤14🔥9👍5
This media is not supported in your browser
VIEW IN TELEGRAM
Бесплатный “one-stop shop” для подготовки к USACO: структурированные разделы от Intro и Bronze до Platinum/Advanced, где в каждом модуле есть объяснение темы, советы по реализации, подборка задач для практики и разборы/редакции
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍8🔥5🤝1
Почему std::move не “перемещает”?
Многие думают, что
Проблема начинается, когда после
После перемещения объект обязан оставаться валидным, но его состояние становится неопределённым (у
Правильное правило простое:
•
• после этого
🔥 Итог:
📣 C++ Ready | #совет
Многие думают, что
std::move(x) физически “переносит” данные и очищает x. Но std::move ничего не двигает — это всего лишь приведение к rvalue-ссылке, то есть сигнал компилятору: “этот объект можно перемещать”.Проблема начинается, когда после
std::move мы продолжаем пользоваться объектом как раньше:std::string s = "hello";
std::string a = std::move(s);
std::cout << s; // ❌ логическая ошибка
После перемещения объект обязан оставаться валидным, но его состояние становится неопределённым (у
std::string часто будет пусто, но полагаться на это нельзя). Поэтому любые “проверю что осталось” и “ещё раз использую” — источник багов.Правильное правило простое:
•
std::move(x) = “я отказываюсь от значения x”• после этого
x можно только: присвоить новое значение / уничтожить / вызвать безопасные операции, не зависящие от содержимого.std::move — не перенос, а разрешение на перенос. Если ты пишешь std::move, считай, что “объект дальше мне не нужен” — так код становится предсказуемым и без скрытых сюрпризов.Please open Telegram to view this post
VIEW IN TELEGRAM
👍22🔥9❤6🤝2
Антипаттерн: # define DEBUG 1 vs NDEBUG
Часто для отладочного кода делают что-то вроде
Работает? Да.
Надёжно? Не всегда.
Проблема в том, что такой
В стандарте C++ уже есть специальный макрос для этого —
Именно на него завязаны
Плохой вариант:
Если забыть убрать
Правильный вариант — ориентироваться на
Теперь всё согласовано:
• debug-сборка → логирование и проверки есть
• release (
Точно так же стоит писать и свои отладочные макросы:
🔥 Использование
📣 C++ Ready | #практика
Часто для отладочного кода делают что-то вроде
#define DEBUG 1 и оборачивают всё в #ifdef DEBUG.Работает? Да.
Надёжно? Не всегда.
Проблема в том, что такой
DEBUG — не стандартный, и он легко расходится с реальным типом сборки. В итоге можно случайно оставить отладочный код в релизе.В стандарте C++ уже есть специальный макрос для этого —
NDEBUG. Если NDEBUG не определён, значит это debug-сборка. Если NDEBUG определён, значит релиз.Именно на него завязаны
assert и многие стандартные проверки.Плохой вариант:
#define DEBUG 1
void foo(int x) {
#ifdef DEBUG
std::cout << "x = " << x << "\n";
#endif
work(x);
}
Если забыть убрать
DEBUG или определить его не там — отладочный код спокойно уедет в прод.Правильный вариант — ориентироваться на
NDEBUG:void foo(int x) {
#ifndef NDEBUG
std::cerr << "x = " << x << "\n";
#endif
work(x);
}Теперь всё согласовано:
• debug-сборка → логирование и проверки есть
• release (
-DNDEBUG) → код полностью вырезается компиляторомТочно так же стоит писать и свои отладочные макросы:
#ifndef NDEBUG
#define DBG_LOG(msg) std::cerr << msg << "\n"
#else
#define DBG_LOG(msg) ((void)0)
#endif
NDEBUG — это не мелочь, а дисциплина сборки: отладочный код никогда не попадёт в релиз случайно, и всё поведение будет согласовано со стандартными механизмами C++.Please open Telegram to view this post
VIEW IN TELEGRAM
❤22👍12🔥7
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥22🤝11❤6👍2
На картинке — компактная шпаргалка по скобкам в Bash:
(), {}, $(), [], [[ ]] — что они делают и чем отличаются (подстановка команд, группировка в текущем shell/подоболочке, массивы, brace expansion, параметрическое расширение, арифметика и проверки условий).Сохрани, чтобы не путаться в синтаксисе и быстрее писать скрипты!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13🔥9👍7👎1
push_back vs emplace_back: когда что использовать?
Обе функции добавляют элемент в контейнер, но делают это по-разному.
Это полезно, когда:
• элемент создаётся “на месте” из нескольких параметров;
• хочется избежать лишнего временного объекта (особенно для тяжёлых типов).
🔥 Важный нюанс: в современном C++ разница часто не драматическая (компилятор умеет оптимизировать временные объекты), так что выбирать стоит по читаемости.
📣 C++ Ready | #совет
Обе функции добавляют элемент в контейнер, но делают это по-разному.
push_back(...) добавляет уже готовый объект. Если передать lvalue — будет копия, если rvalue — будет перемещение:v.push_back(s); // копия
v.push_back(std::move(s)); // move
emplace_back(...) не требует готового объекта — он передаёт аргументы в конструктор элемента и создаёт его *сразу внутри* контейнера:v.emplace_back(5, 'a'); // string("aaaaa") прямо в vectorЭто полезно, когда:
• элемент создаётся “на месте” из нескольких параметров;
• хочется избежать лишнего временного объекта (особенно для тяжёлых типов).
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15❤10👍7
В этой статье:
• Поймёшь, какие микроизменения действительно влияют на скорость• Увидишь примеры про типы, порядок полей, преобразования и “дорогие” операции• Заберёшь идеи про циклы, ветвления и вызовы, чтобы вычищать лишнее без фанатизма🔊 Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11🔥5👍4😁2
Знали что, многие допускают ошибку при использовании оператора delete?
Сегодня как раз ее разберем. Распространенную ошибку в C++ при работе с операторами delete и delete[], она может привести к сбоям программы и утечкам памяти:
Операторы
Для освобождения памяти, выделенной с помощью
Правильное использование delete[]:
Другой распространенный случай — удаление одной и той же памяти дважды. Это может привести к неопределенному поведению:
Установить указатель в nullptr
После того как память освобождена, всегда обнуляйте указатель:
🔥 Правильное использование этих операторов — ключ к предотвращению утечек памяти и ошибок в управлении памятью.
Всегда следите за тем, какой тип памяти вы освобождаете, и избегайте двойного удаления.
📣 C++ Ready | #практика
Сегодня как раз ее разберем. Распространенную ошибку в C++ при работе с операторами delete и delete[], она может привести к сбоям программы и утечкам памяти:
Операторы
delete и delete[] предназначены для освобождения памяти, выделенной с помощью new и new[] соответственно. Часто можно столкнуться с ошибками, если перепутать эти операторы:int* ptr = new int[5]; // Выделили память для массива
delete ptr; // Ошибка! Использован неправильный оператор
Для освобождения памяти, выделенной с помощью
new[], нужно использовать delete[].Правильное использование delete[]:
int* ptr = new int[5]; // Выделили память для массива
delete[] ptr; // Правильно освобождаем память
Другой распространенный случай — удаление одной и той же памяти дважды. Это может привести к неопределенному поведению:
int* ptr = new int[5];
delete[] ptr; // Освободили память
delete[] ptr; // Ошибка: память уже освобождена
Установить указатель в nullptr
После того как память освобождена, всегда обнуляйте указатель:
int* ptr = new int[5];
delete[] ptr;
ptr = nullptr; // Указатель больше не указывает на освобожденную память
🔥 Правильное использование этих операторов — ключ к предотвращению утечек памяти и ошибок в управлении памятью.
Всегда следите за тем, какой тип памяти вы освобождаете, и избегайте двойного удаления.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🔥13❤9
std::span: безопасная альтернатива T* + size!
Очень часто функции принимают «сырые» данные так:
Это быстро, но небезопасно:
• указатель и размер легко рассинхронизировать;
• компилятор никак не помогает;
• легко выйти за границы и словить UB.
В C++20 для этого есть
Перепишем сигнатуру функции:
Теперь функция принимает единое целое, а не два разрозненных аргумента.
Вызывать её можно почти так же просто:
Никаких кастов, никаких размеров вручную — всё выводится автоматически.
Если же нужно изменять данные, достаточно убрать
🔥
📣 C++ Ready | #практика
Очень часто функции принимают «сырые» данные так:
void process(const int* data, std::size_t size);
Это быстро, но небезопасно:
• указатель и размер легко рассинхронизировать;
• компилятор никак не помогает;
• легко выйти за границы и словить UB.
В C++20 для этого есть
std::span — лёгкая обёртка над непрерывным диапазоном памяти. std::span не владеет данными, но всегда знает их размер и тип.Перепишем сигнатуру функции:
#include <span>
void process(std::span<const int> data) {
for (int x : data) {
// безопасный проход по диапазону
work(x);
}
}
Теперь функция принимает единое целое, а не два разрозненных аргумента.
Вызывать её можно почти так же просто:
#include <vector>
#include <array>
int main() {
std::vector<int> v{1, 2, 3};
std::array<int, 3> a{4, 5, 6};
int raw[] = {7, 8, 9};
process(v); // std::vector
process(a); // std::array
process(raw); // C-массив
}
Никаких кастов, никаких размеров вручную — всё выводится автоматически.
Если же нужно изменять данные, достаточно убрать
const:void normalize(std::span<int> data) {
for (int& x : data) {
x = std::max(x, 0);
}
}std::span — это zero-overhead приём: он не копирует данные, не замедляет код и при этом резко снижает шанс UB из-за выхода за границы или перепутанных размеров.Please open Telegram to view this post
VIEW IN TELEGRAM
❤17👍13🔥9😁1