🚀 Станьте C++ разработчиком и откройте для себя новые возможности в IT.
Актуальное обучение от OTUS — это ваш старт в масштабную разработку на современном подмножестве C++!
👨💻 На курсе вы освоите все ключевые аспекты разработки на C++ — от основ синтаксиса до идиом и паттернов языка, продвинутой многопоточности и работы с базами данных Мы подготовим вас для работы с высоконагруженными приложениями, IoT-устройствами и сложными проектами.
⚡️ Изучите C++ с нуля и пройдите два этапа обучения: от Junior до Middle Developer. Реальные кейсы, лучшие практики и советы экспертов помогут вам освоить язык и уверенно претендовать на востребованные позиции.
❗️ Запись на курс закрывается! Оставьте заявку и получите скидку на обучение по промокоду CPPspec_6: https://vk.cc/cN4KR8
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
Актуальное обучение от OTUS — это ваш старт в масштабную разработку на современном подмножестве C++!
👨💻 На курсе вы освоите все ключевые аспекты разработки на C++ — от основ синтаксиса до идиом и паттернов языка, продвинутой многопоточности и работы с базами данных Мы подготовим вас для работы с высоконагруженными приложениями, IoT-устройствами и сложными проектами.
⚡️ Изучите C++ с нуля и пройдите два этапа обучения: от Junior до Middle Developer. Реальные кейсы, лучшие практики и советы экспертов помогут вам освоить язык и уверенно претендовать на востребованные позиции.
❗️ Запись на курс закрывается! Оставьте заявку и получите скидку на обучение по промокоду CPPspec_6: https://vk.cc/cN4KR8
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
👍2
Невидимый UB: возвращаем ссылку на локальную переменную
Одна из самых коварных ошибок в C++ — возврат ссылки на переменную, срок жизни которой закончился. Казалось бы, всё компилируется, запускается... и даже иногда "работает". А под капотом — undefined behavior.
Пример:
Функция возвращает ссылку на
Использование этой ссылки:
👀 Хитрость: такая ошибка часто прячется внутри более сложных функций, и ловится не сразу. Особенно в шаблонном коде или при рефакторинге.
🔒 Как безопасно?
* Возвращайте по значению, если объект небольшой или RVO (return value optimization) работает:
* Или передавайте результат через параметр:
💡 Профит: избежите UB, багов-призраков и бессонных ночей.
➡️ @cpp_geek
Одна из самых коварных ошибок в C++ — возврат ссылки на переменную, срок жизни которой закончился. Казалось бы, всё компилируется, запускается... и даже иногда "работает". А под капотом — undefined behavior.
Пример:
const std::string& getName() {
std::string name = "John";
return name; // 💥 Возвращаем ссылку на локальный объект
}
Функция возвращает ссылку на
name
, но как только getName()
завершится, name
уничтожается. Ссылка указывает в никуда.Использование этой ссылки:
std::cout << getName() << "\n"; // UB: может напечатать мусор, может упасть
👀 Хитрость: такая ошибка часто прячется внутри более сложных функций, и ловится не сразу. Особенно в шаблонном коде или при рефакторинге.
🔒 Как безопасно?
* Возвращайте по значению, если объект небольшой или RVO (return value optimization) работает:
std::string getName() {
std::string name = "John";
return name; // ок, RVO устранит копирование
}
* Или передавайте результат через параметр:
void getName(std::string& out) {
out = "John";
}
💡 Профит: избежите UB, багов-призраков и бессонных ночей.
➡️ @cpp_geek
👍9
🧵 Сегодня покажу вам простой способ логгировать вызовы функций в 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❤3👎1🤔1
Полезные функции, которые будут полезны продвинутым C++ разработчикам.
1.
Универсальная замена
2.
Преобразование чисел с точностью:
3.
RAII-функция для отложенного вызова:
4.
Компилируемая проверка степени двойки:
5.
Для реализации собственного
6.
Полезна для парсинга CSV, логов и т.д.:
7.
Ручной аналог
8.
Упрощённая обёртка над
9.
Интерфейс к
10.
Получение имени типа на этапе компиляции:
➡️ @cpp_geek
1.
void assert_or_throw(bool cond, const std::string& msg)
Универсальная замена
assert
в runtime-среде:
void assert_or_throw(bool cond, const std::string& msg) {
if (!cond) throw std::runtime_error(msg);
}
2.
template<typename T> std::string to_string_precise(const T& val, int precision = 6)
Преобразование чисел с точностью:
template<typename T>
std::string to_string_precise(const T& val, int precision = 6) {
std::ostringstream out;
out << std::fixed << std::setprecision(precision) << val;
return out.str();
}
3.
template<typename F> auto scope_exit(F&& f)
RAII-функция для отложенного вызова:
template<typename F>
class ScopeExit {
F func;
bool active = true;
public:
ScopeExit(F&& f) : func(std::forward<F>(f)) {}
~ScopeExit() { if (active) func(); }
void dismiss() { active = false; }
};
template<typename F>
ScopeExit<F> scope_exit(F&& f) {
return ScopeExit<F>(std::forward<F>(f));
}
4.
template<typename T> constexpr bool is_power_of_two(T x)
Компилируемая проверка степени двойки:
template<typename T>
constexpr bool is_power_of_two(T x) {
return x > 0 && (x & (x - 1)) == 0;
}
5.
template<typename T> void hash_combine(std::size_t& seed, const T& val)
Для реализации собственного
std::hash
:
template<typename T>
void hash_combine(std::size_t& seed, const T& val) {
seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
6.
std::vector<std::string> split(const std::string& str, char delimiter)
Полезна для парсинга CSV, логов и т.д.:
std::vector<std::string> split(const std::string& str, char delimiter) {
std::vector<std::string> out;
std::istringstream ss(str);
std::string token;
while (std::getline(ss, token, delimiter)) {
out.push_back(token);
}
return out;
}
7.
template<typename T> T clamp(T val, T min_val, T max_val)
Ручной аналог
std::clamp
(если нужна совместимость со старым C++):
template<typename T>
T clamp(T val, T min_val, T max_val) {
return std::max(min_val, std::min(val, max_val));
}
8.
template<typename Container, typename Predicate> bool any_of(const Container& c, Predicate pred)
Упрощённая обёртка над
std::any_of
:
template<typename Container, typename Predicate>
bool any_of(const Container& c, Predicate pred) {
return std::any_of(c.begin(), c.end(), pred);
}
9.
template<typename... Args> std::string format(const std::string& fmt, Args&&... args)
Интерфейс к
std::format
(C++20+):
template<typename... Args>
std::string format(const std::string& fmt, Args&&... args) {
return std::vformat(fmt, std::make_format_args(args...));
}
10.
template<typename T> std::string type_name()
Получение имени типа на этапе компиляции:
template<typename T>
std::string type_name() {
#ifdef __clang__
std::string name = __PRETTY_FUNCTION__;
return name.substr(31, name.length() - 32);
#elif defined(__GNUC__)
std::string name = __PRETTY_FUNCTION__;
return name.substr(49, name.length() - 50);
#elif defined(_MSC_VER)
std::string name = __FUNCSIG__;
return name.substr(38, name.length() - 45);
#else
return "unknown";
#endif
}
➡️ @cpp_geek
👍9❤2
std::thread
std::thread является частью стандартной библиотеки C++ и предоставляет возможность создания и управления потоками выполнения. Он позволяет запускать функции в отдельных потоках, обеспечивая параллельное выполнение кода.
Обратите внимание, что после создания потока
В приведенном примере мы использовали
➡️ @cpp_geek
std::thread является частью стандартной библиотеки C++ и предоставляет возможность создания и управления потоками выполнения. Он позволяет запускать функции в отдельных потоках, обеспечивая параллельное выполнение кода.
Обратите внимание, что после создания потока
std::thread
, вы должны вызвать join()
или detach()
для корректной обработки завершения потока.В приведенном примере мы использовали
join()
, чтобы основной поток дождался завершения потока t1. Если вы вызываете join()
после завершения потока, программа может выдать исключение или вызвать неопределенное поведение.➡️ @cpp_geek
🔥9
Узнаем длину аудио файла
В этом примере мы открываем файл с помощью std::ifstream, находим его размер, а затем вычисляем длительность аудио файла, зная частоту дискретизации (в данном случае 44100 Гц). Выводим длительность в минутах и секундах.
Убедитесь, что заменили "your_audio_file.wav" на путь к вашему аудио файлу, а также, если частота дискретизации вашего аудио файла отличается от 44100 Гц, замените это значение на соответствующее значение вашего аудио файла.
➡️ @cpp_geek
В этом примере мы открываем файл с помощью std::ifstream, находим его размер, а затем вычисляем длительность аудио файла, зная частоту дискретизации (в данном случае 44100 Гц). Выводим длительность в минутах и секундах.
Убедитесь, что заменили "your_audio_file.wav" на путь к вашему аудио файлу, а также, если частота дискретизации вашего аудио файла отличается от 44100 Гц, замените это значение на соответствующее значение вашего аудио файла.
➡️ @cpp_geek
👎10👍4
💰Олимпиада по программированию с призовым фондом 50 000 рублей!
🏃💨Для школьников от 10 до 16 лет, задачи можно решать на языках GO, Python, JavaScript, C++
🏆Решить олимпиаду можно 23 июля (среда) с 11:00 до 19:00
🗣Регистрация закроется 20 июля в 23:55
1️⃣ место - 25 000 рублей
2️⃣ место - 15 000 рублей
3️⃣ место - 10 000 рублей
😎Регистрируйся по ссылке, участие бесплатное
Олимпиада ZamaCode
📲А также подписывайся на наш телеграм-канал, чтобы не пропустить новые олимпиады и розыгрыши!
🏃💨Для школьников от 10 до 16 лет, задачи можно решать на языках GO, Python, JavaScript, C++
🏆Решить олимпиаду можно 23 июля (среда) с 11:00 до 19:00
🗣Регистрация закроется 20 июля в 23:55
1️⃣ место - 25 000 рублей
2️⃣ место - 15 000 рублей
3️⃣ место - 10 000 рублей
😎Регистрируйся по ссылке, участие бесплатное
Олимпиада ZamaCode
📲А также подписывайся на наш телеграм-канал, чтобы не пропустить новые олимпиады и розыгрыши!
Почему
Многие думают, что
Вот ловушка:
Почему? Потому что
Но даже тут можно облажаться:
Нужно снова вызвать std::move, если хочешь передать дальше как rvalue:
Правило:
➡️ @cpp_geek
std::move
может не сработатьМногие думают, что
std::move
всегда приводит к перемещению. Но это не так. std::move
не двигает — он просто превращает объект в rvalue-ссылку. Всё остальное — за перегрузками.Вот ловушка:
void take(std::string s) {
// s — копия, даже если передавали std::move
}
std::string str = "hello";
take(std::move(str)); // ❌ копирование, не перемещение
Почему? Потому что
take
принимает по значению, а значит, всегда делает копию. Даже если ты передал std::move(str)
, она скопируется в s
. Чтобы работало перемещение, нужна rvalue-ссылка:
void take(std::string&& s) {
// теперь s — это rvalue-ссылка
}
Но даже тут можно облажаться:
void take(std::string&& s) {
process(s); // ❌ снова копия!
}
void process(std::string); // по значению
Нужно снова вызвать std::move, если хочешь передать дальше как rvalue:
void take(std::string&& s) {
process(std::move(s)); // ✅ перемещение
}
Правило:
std::move
— это обещание, что объект можно "украсть". Но воровать будет только та функция, которая перегружена для rvalue.➡️ @cpp_geek
👍8🤔3❤2👨💻1
Что такое ADL и как она может вас подставить
Argument-Dependent Lookup (ADL) — это фича, которая может как упростить код, так и привести к сюрпризам.
Представьте:
Хотя
Звучит удобно. Но...
⚠️ Опасность:
Такой вызов может внезапно потянуть неожиданные функции, особенно в generic-коде (например, в шаблонах), где ADL может найти «лишнее».
✅ Best practices:
* Не полагайся на ADL без крайней нужды.
* Используй
* В шаблонах — лучше явно передавать функции (через policy, traits, или параметры шаблона).
➡️ @cpp_geek
Argument-Dependent Lookup (ADL) — это фича, которая может как упростить код, так и привести к сюрпризам.
Представьте:
namespace math {
struct Vec {};
void print(const Vec&) {
std::cout << "Vec\n";
}
}
void foo() {
math::Vec v;
print(v); // компилятор найдёт math::print через ADL!
}
Хотя
print
не в текущем скоупе, компилятор ищет её в namespace аргумента v
, т.е. math
. Это и есть ADL.Звучит удобно. Но...
⚠️ Опасность:
namespace lib {
struct Widget {};
void process(const Widget&) {
std::cout << "lib::process\n";
}
}
void process(int) {
std::cout << "global::process\n";
}
void bar() {
lib::Widget w;
process(w); // вызовет lib::process, а не global::process!
}
Такой вызов может внезапно потянуть неожиданные функции, особенно в generic-коде (например, в шаблонах), где ADL может найти «лишнее».
✅ Best practices:
* Не полагайся на ADL без крайней нужды.
* Используй
::qualified::names
для ясности.* В шаблонах — лучше явно передавать функции (через policy, traits, или параметры шаблона).
➡️ @cpp_geek
👍12😁2
Зачем
Кажется,
Пример:
Почему? Потому что:
>
> и
А
Это относится ко всем
👉 Правило: если аргумент — перемещаемый объект (например,
➡️ @cpp_geek
std::move
от std::string
в emplace_back
?Кажется,
emplace_back(std::move(str))
— избыточно: ведь emplace_back
сам конструирует объект на месте. Но с std::string
и другими перемещаемыми типами всё не так просто.Пример:
std::vector<std::string> vec;
std::string str = "hello";
vec.emplace_back(str); // копия
vec.emplace_back(std::move(str)); // перемещение
Почему? Потому что:
>
emplace_back
вызывает конструктор std::string(const std::string&)
, если аргумент — const std::string&
,> и
std::string(std::string&&)
, если аргумент — std::string&&
.А
str
— это lvalue. Даже внутри emplace_back
. Без std::move
, ты просишь вектор скопировать
строку. С std::move
— переместить
.Это относится ко всем
emplace_*
, когда ты передаёшь существующий объект. emplace_back("hello")
— другое дело: тут создаётся временный const char*
, и он уже используется для конструктора string
.👉 Правило: если аргумент — перемещаемый объект (например,
std::string
, std::vector), и ты передаёшь его в emplace_*
, не забывай std::move
. Иначе будет лишняя копия.➡️ @cpp_geek
👍5
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👌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❤1
Невидимый враг:
Кажется, что
Под капотом
- Элемент — это не
- Нельзя получить настоящий указатель на элемент:
- Код работает медленнее из-за лишней возни с битовыми масками.
Пример неожиданности:
Хотите реально быстрый и предсказуемый контейнер?
- Используйте
- Или подключите
Итог:
➡️ @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