Зачем
Если ты думаешь, что
Пример:
А теперь магия с методами:
📌 Даже если функция возвращает
⚠️ Но аккуратно:
Если хочешь писать более надёжный и самодокументируемый код — юзай
➡️ @cpp_geek
[[nodiscard]]
нужен не только для возврата значенияЕсли ты думаешь, что
[[nodiscard]]
— это просто защита от игнора Result
, то вот фокус: его можно вешать и на классы, и на функции, и даже на enum — и это реально помогает избежать багов.Пример:
struct [[nodiscard]] Error {
std::string message;
};
Error do_something() {
return {"Что-то пошло не так"};
}
void foo() {
do_something(); // warning: ignoring return value of nodiscard type
}
А теперь магия с методами:
struct Connection {
[[nodiscard]] bool is_valid() const {
return valid_;
}
private:
bool valid_ = false;
};
void check_connection(const Connection& conn) {
conn.is_valid(); // warning: result of 'is_valid' is unused
}
📌 Даже если функция возвращает
bool
— компилятор предупредит, если ты его проигнорируешь. Это круто, когда метод что-то проверяет, ищет или сигналит об ошибке — и ты точно не хочешь забыть проверить результат.⚠️ Но аккуратно:
[[nodiscard]]
не бросает исключения и не делает функцию безопасной. Это подсказка компилятору и твой напарник по коду.Если хочешь писать более надёжный и самодокументируемый код — юзай
[[nodiscard]]
не только по дефолту, а осознанно.➡️ @cpp_geek
👍8🔥1
Ключевые библиотеки Boost, которые полезно знать каждому C++ разработчику
Вот топ-5 библиотек Boost:
1️⃣ Boost.Asio
Асинхронный ввод-вывод и сетевое программирование. Незаменим для серверных приложений.
2️⃣ Boost.Beast
HTTP и WebSocket клиенты/серверы. Построен на Asio.
3️⃣ Boost.Serialization
Сериализация сложных структур данных в потоки байтов и обратно.
4️⃣ Boost.Graph
Алгоритмы на графах: поиск путей, обходы, топологическая сортировка и др.
5️⃣ Boost.Spirit
Создание парсеров прямо в коде C++ без внешних генераторов.
➡️ @cpp_geek
Вот топ-5 библиотек Boost:
1️⃣ Boost.Asio
Асинхронный ввод-вывод и сетевое программирование. Незаменим для серверных приложений.
2️⃣ Boost.Beast
HTTP и WebSocket клиенты/серверы. Построен на Asio.
3️⃣ Boost.Serialization
Сериализация сложных структур данных в потоки байтов и обратно.
4️⃣ Boost.Graph
Алгоритмы на графах: поиск путей, обходы, топологическая сортировка и др.
5️⃣ Boost.Spirit
Создание парсеров прямо в коде C++ без внешних генераторов.
➡️ @cpp_geek
👍7🔥1
Тема: Почему
На первый взгляд
Пример:
* Нельзя взять адрес элемента
* Нельзя использовать
* Некоторые шаблоны не работают (особенно в generic-коде)
Хочешь экономии — будь готов к сюрпризам. Хочешь предсказуемости — используй
⚠️ Кстати,
➡️ @cpp_geek
std::vector<bool>
- не совсем std::vector
На первый взгляд
std::vector<bool>
— обычный вектор, только из булевых значений. Но это особенный шаблон. Вместо хранения bool
как полноценного байта, он упаковывает их в биты. Экономит память? Да. Но есть нюансы.Пример:
std::vector<bool> flags = {true, false, true};
auto x = flags[0]; // Не bool, а прокси-объект!
bool y = flags[0]; // OK — копия значения
bool& z = flags[0]; // Ошибка компиляции
flags[0]
возвращает proxy-объект, а не bool&
, потому что нельзя вернуть ссылку на бит. Из-за этого:* Нельзя взять адрес элемента
* Нельзя использовать
std::vector<bool>
с API, ожидающим bool*
или bool&
* Некоторые шаблоны не работают (особенно в generic-коде)
Хочешь экономии — будь готов к сюрпризам. Хочешь предсказуемости — используй
std::deque<bool>
или std::vector<char>
.⚠️ Кстати,
std::vector<bool>
— единственная специализация STL-контейнера в стандартной библиотеке.➡️ @cpp_geek
👍9❤1🔥1
🚀 Станьте 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❤1👎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❤1
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❤1👨💻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
👍11😁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
This media is not supported in your browser
VIEW IN TELEGRAM
2 августа Яндекс проведет C++ Zero Cost Conf: конференцию о прикладном C++
Мероприятие для практикующих C++ разработчиков, где профессионалы из ведущих компаний поделятся реальными кейсами. В Москве (офлайн+онлайн), в Белграде (офлайн+онлайн) и Санкт-Петербурге (только офлайн).
Часть программы в Москве:
— «Алиасинг памяти в компиляторе и в вашей программе»: глубокий разбор строгого алиасинга и его влияния на производительность.
Спикеры: Константин Владимиров и Владислав Белов, Syntacore.
— «Performance Puzzlers»: анализируем производительность и учимся извлекать пользу через эксперименты с пайплайнами, векторизацией и не только.
Спикер: Сергей Слотин, C++ эксперт и автор «Алгоритмики».
— «C++20 Модули — практическое внедрение»: как интегрировать C++20 модули в большие существующие проекты с поддержкой старых стандартов.
Спикер: Антон Полухин, Техплатформа Городских сервисов Яндекса.
Полный список докладов по городам на сайте.
Помимо этого, в Москве участники смогут поучаствовать в код-гольфе на C++, в воркшопе по Perforator и решить реальные задачи по надежности сервисов в Case Lab. Код-ревью пройдет во всех городах.
Регистрация открыта! Участие бесплатно.
Мероприятие для практикующих C++ разработчиков, где профессионалы из ведущих компаний поделятся реальными кейсами. В Москве (офлайн+онлайн), в Белграде (офлайн+онлайн) и Санкт-Петербурге (только офлайн).
Часть программы в Москве:
— «Алиасинг памяти в компиляторе и в вашей программе»: глубокий разбор строгого алиасинга и его влияния на производительность.
Спикеры: Константин Владимиров и Владислав Белов, Syntacore.
— «Performance Puzzlers»: анализируем производительность и учимся извлекать пользу через эксперименты с пайплайнами, векторизацией и не только.
Спикер: Сергей Слотин, C++ эксперт и автор «Алгоритмики».
— «C++20 Модули — практическое внедрение»: как интегрировать C++20 модули в большие существующие проекты с поддержкой старых стандартов.
Спикер: Антон Полухин, Техплатформа Городских сервисов Яндекса.
Полный список докладов по городам на сайте.
Помимо этого, в Москве участники смогут поучаствовать в код-гольфе на C++, в воркшопе по Perforator и решить реальные задачи по надежности сервисов в Case Lab. Код-ревью пройдет во всех городах.
Регистрация открыта! Участие бесплатно.
❤4👍3🔥2
Почему
Если вы думаете, что
Чтобы экономить память, стандартная библиотека делает его специализацией, хранящей биты упакованно. Цена — нетипичное поведение:
Элементы
- нельзя получить указатель на элемент;
- работа с ними медленнее, чем с
- поведение иногда ломает шаблонный код, который ожидает нормальные ссылки и указатели.
Что делать?
Если вам нужна коллекция булей — берите
➡️ @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
👍11❤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
👍7💩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
👍10❤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