C++ geek
3.58K subscribers
254 photos
4 videos
19 links
Учим C/C++ на примерах
Download Telegram
Сейчас покажу вам простой, но очень полезный приём, как аккуратно и безопасно управлять ресурсами в C++ с помощью RAII (Resource Acquisition Is Initialization).

Когда вы работаете с ресурсами (файлы, сокеты, мьютексы и т.д.), важно не забывать освобождать их. Особенно если программа может завершиться по исключению. И вот тут RAII — наш лучший друг.

Рассмотрим пример:


#include <fstream>
#include <string>

void writeToFile(const std::string& filename, const std::string& data) {
std::ofstream file(filename);
if (!file) {
throw std::runtime_error("Unable to open file");
}
file << data;
} // файл автоматически закрывается здесь


Мы открыли файл — и не закрыли его вручную! Почему? Потому что std::ofstream сам закроет его в своём деструкторе. Это и есть RAII в действии.

И теперь представьте: вы можете создавать свои классы с таким же поведением! Например, класс-обёртку над pthread_mutex_t или системным дескриптором.

RAII — это стиль. И это стиль надёжного кода.


➡️ @cpp_geek
👍71💅1
🧵 Сегодня покажу вам простой способ логгировать вызовы функций в C++ — пригодится для отладки и анализа кода.

Часто бывает нужно понять, какие функции вызываются, в каком порядке и с какими параметрами. Вручную вставлять std::cout — неудобно. Вместо этого используем RAII-макрос с выводом в консоль:


#include <iostream>
#include <string>

struct FunctionLogger {
std::string func_name;
FunctionLogger(const std::string& name) : func_name(name) {
std::cout << ">> Entering: " << func_name << '\n';
}
~FunctionLogger() {
std::cout << "<< Exiting: " << func_name << '\n';
}
};

#define LOG_FUNCTION() FunctionLogger logger(__FUNCTION__)


Теперь в любой функции достаточно просто написать LOG_FUNCTION();, и вы получите автоматический лог при входе и выходе:


void do_work() {
LOG_FUNCTION();
// Работаем...
}


Это особенно удобно в больших проектах, когда нужно быстро локализовать ошибку или понять структуру вызовов.

Можно доработать: лог в файл, потокобезопасность, включение по флагу компиляции и т.д.

➡️ @cpp_geek
👍162👎1🤔1
Move-семантика: где можно ловко сэкономить

Многие знают про std::move, но не всегда используют его там, где это реально ускоряет код. Простой пример — возврат локального объекта из функции:


#include <string>

std::string make_string() {
std::string s = "Hello";
return s; // RVO или move
}


С C++17 тут почти всегда RVO (Return Value Optimization). Но если RVO невозможен (например, возвращаем тернарный оператор), компилятор применит move:


std::string make_string(bool flag) {
std::string a = "foo", b = "bar";
return flag ? a : b; // тут будет move
}


А вот так можно подсказать компилятору явно:


return std::move(flag ? a : b);


Но осторожно: не делайте std::move для локальной переменной в простом return — это может сломать RVO и привести к лишнему перемещению.

Ещё полезно помнить: move не всегда бесплатный. Например, для std::vector он копирует указатель и размер, но не элементы. Для std::string — зависит от Small String Optimization: короткие строки перемещаются как копия.

Вывод: используйте std::move там, где явно хотите отдать объект, а не копировать. Но не злоупотребляйте им: компилятор с C++17 сам неплохо справляется.

➡️ @cpp_geek
👍4
RAII — твой лучший друг (и почему не стоит бояться умных указателей)

Старый добрый 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
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
3👍3
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
🔥4🥴4
🧵 Сегодня покажу вам, как удобно логгировать значения в отладке с помощью макроса, который реально упрощает жизнь.

Когда мы отлаживаем код, часто хочется быстро вывести значения переменных. Писать 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
🔥9👍5