RAII — твой лучший друг (и почему не стоит бояться умных указателей)
Старый добрый
Решение — RAII (Resource Acquisition Is Initialization): ресурсы живут ровно столько, сколько объект, который ими владеет. Ушёл объект из области видимости — ресурс освободился.
Пример с умными указателями:
Что важно знать:
*
*
* Никогда не делай
RAII работает не только для памяти: файлы, мьютексы, сокеты — всё. Достаточно обернуть ресурс в класс с деструктором.
Профит: меньше багов, меньше утечек, чище код.
➡️ @cpp_geek
Старый добрый
new
/
delete
— это классика, но и источник утечек, крашей и боли. В современном C++ ручное управление памятью почти всегда антипаттерн.Решение — RAII (Resource Acquisition Is Initialization): ресурсы живут ровно столько, сколько объект, который ими владеет. Ушёл объект из области видимости — ресурс освободился.
Пример с умными указателями:
#include <memory>
#include <iostream>
struct Foo {
Foo() { std::cout << "Init\n"; }
~Foo() { std::cout << "Destroy\n"; }
};
void bar() {
std::unique_ptr<Foo> p = std::make_unique<Foo>(); // RAII
// делаем что-то
} // тут автоматически вызовется ~Foo()
Что важно знать:
*
std::unique_ptr
— владение в единственном числе, идеально для большинства случаев.*
std::shared_ptr
— разделённое владение (но дороже по производительности).* Никогда не делай
new
без обёртки — почти всегда лучше std::make_unique
или std::make_shared
.RAII работает не только для памяти: файлы, мьютексы, сокеты — всё. Достаточно обернуть ресурс в класс с деструктором.
Профит: меньше багов, меньше утечек, чище код.
➡️ @cpp_geek
❤3👍3👌1
Почему
Если вы думаете, что
Чтобы экономить память, стандартная библиотека делает его специализацией, хранящей биты упакованно. Цена — нетипичное поведение:
Элементы
- нельзя получить указатель на элемент;
- работа с ними медленнее, чем с
- поведение иногда ломает шаблонный код, который ожидает нормальные ссылки и указатели.
Что делать?
Если вам нужна коллекция булей — берите
➡️ @cpp_geek
std::vector<bool>
— это ловушкаЕсли вы думаете, что
std::vector<bool>
— обычный вектор, только для bool
, то… сюрприз! Это не совсем так.Чтобы экономить память, стандартная библиотека делает его специализацией, хранящей биты упакованно. Цена — нетипичное поведение:
std::vector<bool> v{true, false, true};
bool* p = &v[0]; // Ошибка! Тут нет обычного bool*
Элементы
vector<bool>
— это не настоящие bool
, а прокси-объекты. Они ведут себя как bool
, но фактически это обёртки над битами. Итог:- нельзя получить указатель на элемент;
- работа с ними медленнее, чем с
bool
;- поведение иногда ломает шаблонный код, который ожидает нормальные ссылки и указатели.
Что делать?
Если вам нужна коллекция булей — берите
std::vector<char>
или std::vector<uint8_t>
. Памяти уйдёт чуть больше, но всё будет предсказуемо и быстро.std::vector<bool>
стоит использовать только если критична экономия памяти, и вы понимаете все его подводные камни.➡️ @cpp_geek
👍12❤2
Невидимый враг:
Кажется, что
Под капотом
- Элемент — это не
- Нельзя получить настоящий указатель на элемент:
- Код работает медленнее из-за лишней возни с битовыми масками.
Пример неожиданности:
Хотите реально быстрый и предсказуемый контейнер?
- Используйте
- Или подключите
Итог:
➡️ @cpp_geek
std::vector<bool>
Кажется, что
std::vector<bool>
— обычный вектор из bool. Но на самом деле это не так.Под капотом
vector<bool>
хранит биты плотно упакованными (1 бит на элемент), а не как bool
(обычно 1 байт). Из-за этого:- Элемент — это не
bool&
, а прокси-объект (std::vector<bool>::reference
).- Нельзя получить настоящий указатель на элемент:
&v[0]
не даст bool*
.- Код работает медленнее из-за лишней возни с битовыми масками.
Пример неожиданности:
std::vector<bool> v{true, false};
auto p = &v[0]; // ❌ Ошибка! Нет bool*
Хотите реально быстрый и предсказуемый контейнер?
- Используйте
std::vector<char>
или std::vector<uint8_t>
для хранения флагов.- Или подключите
boost::dynamic_bitset
, если нужна именно битовая упаковка.Итог:
vector<bool>
— это костыль ради экономии памяти. Если важны скорость и нормальная семантика — обходите стороной.➡️ @cpp_geek
👍8💩3
Зачем
Сколько раз вы видели: функция возвращает значение, а кто-то его тупо игнорирует. А потом баги из ниоткуда. В C++17 завезли
Компилятор предупреждает: "эй, ты вызвал, но не используешь результат".
Где это особенно важно:
- Функции, которые возвращают ошибки (
- Функции, где пропуск результата ломает логику (например, RAII-объекты, токены отмены, хендлы).
Можно навесить
Вместо неочевидных багов — раннее предупреждение компилятора.
⚡ Лайфхак: Если компилятор слишком шумит — можно сделать
Вывод: ставьте
➡️ @cpp_geek
[[nodiscard]]
и когда он реально спасает?Сколько раз вы видели: функция возвращает значение, а кто-то его тупо игнорирует. А потом баги из ниоткуда. В C++17 завезли
[[nodiscard]]
, и это реально полезная штука.
[[nodiscard]] int compute() {
return 42;
}
int main() {
compute(); // warning: ignoring return value
}
Компилятор предупреждает: "эй, ты вызвал, но не используешь результат".
Где это особенно важно:
- Функции, которые возвращают ошибки (
std::error_code
, std::optional
, bool
успеха операции);- Функции, где пропуск результата ломает логику (например, RAII-объекты, токены отмены, хендлы).
Можно навесить
[[nodiscard]]
и на типы (C++20):
struct [[nodiscard]] Result {
bool ok;
};
Result foo();
foo(); // warning!
Вместо неочевидных багов — раннее предупреждение компилятора.
⚡ Лайфхак: Если компилятор слишком шумит — можно сделать
[[nodiscard("объяснение")]]
(C++20), чтобы явно подсказать, зачем это.Вывод: ставьте
[[nodiscard]]
на всё, что нельзя безнаказанно проигнорировать. Это дешёвая страховка от глупых багов.➡️ @cpp_geek
👍11❤4
std::exchange
— простой способ менять значения и возвращать старыеВместо того чтобы писать руками:
auto old = value;
value = new_value;
return old;
В modern C++ есть готовый инструмент -
std::exchange
(C++14+).
#include <utility>
#include <string>
#include <iostream>
int main() {
std::string s = "Hello";
auto old = std::exchange(s, "World");
std::cout << "old = " << old << ", s = " << s << '\n';
}
Вывод:
old = Hello, s = World
Когда полезно:
- Реализация move-конструкторов/операторов:
MyType(MyType&& other)
: data_(std::exchange(other.data_, nullptr)) {}
- Сброс состояния объектов и возвращение старого значения.
- Реализация одноразовых флагов (`once_flag` паттерн).
Плюсы:
- Одна строка вместо трёх.
- Читаемость выше — сразу видно, что мы заменяем значение и берём старое.
Помни: по умолчанию второе значение копируется/перемещается, так что это не нулевой по стоимости вызов.
➡️ @cpp_geek
👍4❤3
Как уменьшить время компиляции C++ проектов
Сегодня я хочу поговорить про боль, которую мы все знаем — долгую компиляцию больших C++ проектов.
Когда проект растёт, время сборки иногда становится просто катастрофическим. Вот несколько приёмов, которые реально помогают:
1. PCH (Precompiled Headers) — вынесите редко меняющийся код (например,
2. Разделяйте код на интерфейсы и реализации — заголовки должны быть минимальными, только объявления. Всё, что можно, уносите в
3. Используйте
4. Минимизируйте include-цепочки — подключайте в заголовках только то, что реально нужно. Остальное — в
5. Инкрементальная сборка — убедитесь, что сборочная система (CMake, Ninja, Make) не пересобирает лишнее.
Когда я внедрял эти подходы в одном проекте, время компиляции сократилось с 18 минут до 6. Это реально чувствуется.
➡️ @cpp_geek
Сегодня я хочу поговорить про боль, которую мы все знаем — долгую компиляцию больших C++ проектов.
Когда проект растёт, время сборки иногда становится просто катастрофическим. Вот несколько приёмов, которые реально помогают:
1. PCH (Precompiled Headers) — вынесите редко меняющийся код (например,
<iostream>
, <vector>
, <string>
) в precompiled header. Это может срезать время компиляции в разы.2. Разделяйте код на интерфейсы и реализации — заголовки должны быть минимальными, только объявления. Всё, что можно, уносите в
.cpp
.3. Используйте
#pragma once
вместо include guard — чуть быстрее и проще.4. Минимизируйте include-цепочки — подключайте в заголовках только то, что реально нужно. Остальное — в
.cpp
.5. Инкрементальная сборка — убедитесь, что сборочная система (CMake, Ninja, Make) не пересобирает лишнее.
Когда я внедрял эти подходы в одном проекте, время компиляции сократилось с 18 минут до 6. Это реально чувствуется.
➡️ @cpp_geek
🔥7👍2
std::move
vs std::forward
: когда и что использоватьСегодня покажу вам коротко, чем отличаются std::move и std::forward.
- std::move(obj) — безусловно превращает объект в rvalue. После этого объект считается "пустым" для повторного использования (в рамках контракта move). Используем, когда мы точно хотим "забрать" ресурсы.
- std::forward<T>(obj) — условно делает rvalue, если изначально пришёл rvalue. То есть это "perfect forwarding" для шаблонных функций.
Пример:
#include <utility>
#include <string>
#include <iostream>
template <typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // сохраняет rvalue/lvalue-семантику
}
void process(const std::string& s) { std::cout << "Lvalue: " << s << '\n'; }
void process(std::string&& s) { std::cout << "Rvalue: " << s << '\n'; }
int main() {
std::string str = "Hello";
wrapper(str); // Lvalue
wrapper(std::move(str)); // Rvalue
}
Запомните:
-
std::move
- "забрать".-
std::forward
- "передать как есть".➡️ @cpp_geek
🔥5🥴4
🧵 Сегодня покажу вам, как удобно логгировать значения в отладке с помощью макроса, который реально упрощает жизнь.
Когда мы отлаживаем код, часто хочется быстро вывести значения переменных. Писать
Фишка в том, что
🔥 Такой макрос отлично заходит при написании алгоритмов, отладки функций, проверки значений — и при этом делает код аккуратнее.
Хочешь улучшить - можно сделать вывод в файл или добавить таймстемпы.
➡️ @cpp_geek
Когда мы отлаживаем код, часто хочется быстро вывести значения переменных. Писать
std::cout << "x: " << x << std::endl;
каждый раз — боль. Давайте упростим:
#include <iostream>
#define LOG(var) std::cout << #var << " = " << (var) << std::endl;
int main() {
int x = 42;
double pi = 3.1415;
LOG(x); // x = 42
LOG(pi); // pi = 3.1415
}
Фишка в том, что
#var
превращает имя переменной в строку. А (var)
- значение.🔥 Такой макрос отлично заходит при написании алгоритмов, отладки функций, проверки значений — и при этом делает код аккуратнее.
Хочешь улучшить - можно сделать вывод в файл или добавить таймстемпы.
➡️ @cpp_geek
🔥12👍5🗿1
Media is too big
VIEW IN TELEGRAM
🚀 YADRO приглашает C++ разработчиков в команду OpenBMC и встроенных систем!
Если вы хотите создавать сложное программное обеспечение для серверов и систем хранения данных, работать с передовыми технологиями Linux и участвовать в проектах open source, то эта возможность для вас.
📌 Кого мы ищем:
• Ведущего разработчика C++ (Linux/OpenBMC)
• Ведущего разработчика интерфейсов встроенных систем
• TeamLead разработки OpenBMC
🧰 Технологический стек и задачи:
• C++ (стандарты 17, 20, 23), STL, Boost
• Linux-среда, systemd, D-Bus, Yocto, bash, Python
• Работа с ядром прошивки OpenBMC, взаимодействие с UEFI/BIOS
• Разработка и поддержка сложных интерфейсов встроенных систем
💼 Условия работы:
• Гибкий формат: удалённо или в офисах в Москве, Санкт-Петербурге, Екатеринбурге, Нижнем Новгороде и Минске
• Работа с масштабными проектами в уникальной команде инженеров
• Возможность горизонтального и вертикального карьерного роста
💙 Узнайте больше и откликайтесь на вакансии прямо на сайте!
Если вы хотите создавать сложное программное обеспечение для серверов и систем хранения данных, работать с передовыми технологиями Linux и участвовать в проектах open source, то эта возможность для вас.
📌 Кого мы ищем:
• Ведущего разработчика C++ (Linux/OpenBMC)
• Ведущего разработчика интерфейсов встроенных систем
• TeamLead разработки OpenBMC
🧰 Технологический стек и задачи:
• C++ (стандарты 17, 20, 23), STL, Boost
• Linux-среда, systemd, D-Bus, Yocto, bash, Python
• Работа с ядром прошивки OpenBMC, взаимодействие с UEFI/BIOS
• Разработка и поддержка сложных интерфейсов встроенных систем
💼 Условия работы:
• Гибкий формат: удалённо или в офисах в Москве, Санкт-Петербурге, Екатеринбурге, Нижнем Новгороде и Минске
• Работа с масштабными проектами в уникальной команде инженеров
• Возможность горизонтального и вертикального карьерного роста
Please open Telegram to view this post
VIEW IN TELEGRAM
🖕4❤3😁1
Что такое виртуальный деструктор и зачем он используется в C++?
В C++ виртуальный деструктор используется для правильного освобождения памяти при удалении объекта через указатель на базовый класс. Если базовый класс имеет виртуальный деструктор, то при удалении объекта через указатель на базовый класс будет вызван деструктор не только базового класса, но и всех его производных классов. Это позволяет избежать утечек памяти и неопределенного поведения при работе с полиморфными объектами.
Если виртуального деструктора не объявлено в базовом классе, то при удалении производного объекта через указатель на базовый класс будут вызваны только деструкторы базового класса, что может привести к утечкам памяти и неопределенному поведению.
➡️ @cpp_geek
В C++ виртуальный деструктор используется для правильного освобождения памяти при удалении объекта через указатель на базовый класс. Если базовый класс имеет виртуальный деструктор, то при удалении объекта через указатель на базовый класс будет вызван деструктор не только базового класса, но и всех его производных классов. Это позволяет избежать утечек памяти и неопределенного поведения при работе с полиморфными объектами.
Если виртуального деструктора не объявлено в базовом классе, то при удалении производного объекта через указатель на базовый класс будут вызваны только деструкторы базового класса, что может привести к утечкам памяти и неопределенному поведению.
➡️ @cpp_geek
👍6❤2🤯2
Изменяемые лямбда-функции
Ключевое слово mutable используется для сохранения состояния в лямбда-функциях. Обычно оператор вызова функции замыкания является константным. Другими словами — лямбда не может модифицировать переменные, захваченные по значению.
Но ключевое слово
Следует заметить, что в отличии от mutable-переменных в объявлении класса, мутабельные лямбда-функции должны использоваться относительно редко и очень аккуратно. Сохранение состояния между вызовами лямбда-функции может быть опасным и контринтуитивным.
➡️ @cpp_geek
Ключевое слово mutable используется для сохранения состояния в лямбда-функциях. Обычно оператор вызова функции замыкания является константным. Другими словами — лямбда не может модифицировать переменные, захваченные по значению.
Но ключевое слово
mutable
может быть применено ко всей лямбда-функции, что сделает все её переменные изменяемыми.Следует заметить, что в отличии от mutable-переменных в объявлении класса, мутабельные лямбда-функции должны использоваться относительно редко и очень аккуратно. Сохранение состояния между вызовами лямбда-функции может быть опасным и контринтуитивным.
➡️ @cpp_geek
👍2❤1😱1
Сейчас покажу вам простой, но мощный приём для ускорения компиляции - разделение объявления и реализации шаблонов с использованием
Все мы знаем, что шаблоны в C++ реализуются в заголовочных файлах. Это значит, что каждый
🔧 Что можно сделать?
Разделяем интерфейс и реализацию:
Теперь в клиентском коде:
✅ Это даст:
- Сокращение времени компиляции
- Чище зависимости
- Потенциально меньший размер бинарника
Но помните:
➡️ @cpp_geek
explicit instantiation
.Все мы знаем, что шаблоны в C++ реализуются в заголовочных файлах. Это значит, что каждый
.cpp
файл, который включает такой заголовок, заново инстанцирует шаблон. Результат - долгое время компиляции и раздутый бинарник.🔧 Что можно сделать?
Разделяем интерфейс и реализацию:
// MyTemplate.hpp
#pragma once
template<typename T>
class MyTemplate {
public:
void doSomething();
};
// MyTemplate.cpp
#include "MyTemplate.hpp"
#include <iostream>
template<typename T>
void MyTemplate<T>::doSomething() {
std::cout << "Doing something\n";
}
// Явная инстанциация
template class MyTemplate<int>;
Теперь в клиентском коде:
#include "MyTemplate.hpp"
int main() {
MyTemplate<int> obj;
obj.doSomething();
}
✅ Это даст:
- Сокращение времени компиляции
- Чище зависимости
- Потенциально меньший размер бинарника
Но помните:
explicit instantiation
работает, только если вы заранее знаете типы, с которыми будете использовать шаблон.➡️ @cpp_geek
👍8😱2
Генерируем пароли с помощью C++
Эта программа создает константы для допустимых символов в пароле и для длины пароля. Затем она инициализирует генератор случайных чисел и генерирует пять случайных паролей.
Каждый пароль формируется путем выбора случайного символа из всего набора символов. Этот процесс повторяется до достижения желаемой длины пароля.
➡️ @cpp_geek
Эта программа создает константы для допустимых символов в пароле и для длины пароля. Затем она инициализирует генератор случайных чисел и генерирует пять случайных паролей.
Каждый пароль формируется путем выбора случайного символа из всего набора символов. Этот процесс повторяется до достижения желаемой длины пароля.
➡️ @cpp_geek
👍9🤣1
memmove
Функция memmove используется для копирования блока памяти из одного места в другое. Она объявлена в заголовочном файле. Она принимает аргументы типа
memmove может обрабатывать перекрывающиеся буферы. В отличие от memcpy, которая просто копирует данные из одного места в другое, memmove может безопасно перемещать данные, даже если исходный и целевой буферы перекрываются.
Функция memmove может быть полезна для удаления элементов из массива. Например, если вы хотите удалить элемент из массива и сдвинуть оставшиеся элементы влево, вы можете использовать memmove для перемещения данных в массиве.
➡️ @cpp_geek
Функция memmove используется для копирования блока памяти из одного места в другое. Она объявлена в заголовочном файле. Она принимает аргументы типа
void *
и const void *
, что позволяет ей работать с любыми типами данных. Она просто копирует указанное количество байтов из исходного буфера в целевой.memmove может обрабатывать перекрывающиеся буферы. В отличие от memcpy, которая просто копирует данные из одного места в другое, memmove может безопасно перемещать данные, даже если исходный и целевой буферы перекрываются.
Функция memmove может быть полезна для удаления элементов из массива. Например, если вы хотите удалить элемент из массива и сдвинуть оставшиеся элементы влево, вы можете использовать memmove для перемещения данных в массиве.
➡️ @cpp_geek
❤🔥1👍1
Move-only объекты и почему
Многие удивляются, когда компилятор ругается: «
Ключевой момент: после
Если нужен shareable ресурс – используйте
Частая ошибка на собеседованиях: «почему нельзя вернуть
👉 Правило: если объект владеет чем-то уникально – делайте его move-only (удалите копирование). Это повышает безопасность кода и явно выражает семантику владения.
➡️ @cpp_geek
std::unique_ptr
нельзя копироватьМногие удивляются, когда компилятор ругается: «
unique_ptr
не имеет конструктора копирования». Это не баг, а фича: он move-only. Логика простая: владелец ресурса должен быть только один.
#include <memory>
#include <iostream>
int main() {
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// std::unique_ptr<int> p2 = p1; // ❌ ошибка копирования
std::unique_ptr<int> p2 = std::move(p1); // ✅ перенос
std::cout << *p2 << "\n"; // 42
std::cout << (p1 ? "not null" : "null") << "\n"; // null
}
Ключевой момент: после
std::move
старый указатель «обнуляется», чтобы избежать двойного освобождения памяти.Если нужен shareable ресурс – используйте
std::shared_ptr
. Но помните: это дороже (счётчик ссылок, атомики).Частая ошибка на собеседованиях: «почему нельзя вернуть
unique_ptr
по значению?» - на самом деле можно! Он спокойно двигается:
std::unique_ptr<int> make_ptr() {
return std::make_unique<int>(99); // move происходит неявно
}
👉 Правило: если объект владеет чем-то уникально – делайте его move-only (удалите копирование). Это повышает безопасность кода и явно выражает семантику владения.
➡️ @cpp_geek
👍4😁3