🧵 Сегодня покажу вам простой способ логгировать вызовы функций в C++ — пригодится для отладки и анализа кода.
Часто бывает нужно понять, какие функции вызываются, в каком порядке и с какими параметрами. Вручную вставлять
Теперь в любой функции достаточно просто написать
Это особенно удобно в больших проектах, когда нужно быстро локализовать ошибку или понять структуру вызовов.
Можно доработать: лог в файл, потокобезопасность, включение по флагу компиляции и т.д.
➡️ @cpp_geek
Часто бывает нужно понять, какие функции вызываются, в каком порядке и с какими параметрами. Вручную вставлять
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
👍16❤2👎1🤔1
Move-семантика: где можно ловко сэкономить
Многие знают про
С C++17 тут почти всегда RVO (Return Value Optimization). Но если RVO невозможен (например, возвращаем тернарный оператор), компилятор применит move:
А вот так можно подсказать компилятору явно:
Но осторожно: не делайте
Ещё полезно помнить: move не всегда бесплатный. Например, для
Вывод: используйте
➡️ @cpp_geek
Многие знают про
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 — твой лучший друг (и почему не стоит бояться умных указателей)
Старый добрый
Решение — 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
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❤1