C/C++ Ready | Программирование
15.8K subscribers
1.5K photos
94 videos
751 links
Авторский канал по разработке на C и C++.
Ресурсы, гайды, задачи, шпаргалки.
Информация ежедневно пополняется!

Cотрудничество: @energy_c

РКН: https://clck.ru/3QREHc
Download Telegram
Почему unique_ptr::release() может оставить объект в памяти навсегда?

Обычно unique_ptr удаляет объект, когда владелец выходит из области видимости. Это одна из причин, почему его удобно возвращать из функций и хранить в классе:
auto ptr = std::make_unique<Widget>();


Но у unique_ptr есть метод release. Название звучит так, будто он освобождает память:
ptr.release();


На самом деле release освобождает unique_ptr от владения объектом. Он возвращает обычный указатель и оставляет ответственность за удаление вызывающему коду. Если результат проигнорировать, объект больше никто не удалит.

Когда нужен только временный доступ без передачи владения, используйте get:
Widget* borrowed = ptr.get();
use(*borrowed);


ptr по-прежнему владеет объектом. Сохранить borrowed после уничтожения ptr нельзя, зато вручную удалять его тоже не нужно.

Если объект нужно уничтожить прямо сейчас, подойдет reset:
ptr.reset();


После reset указатель станет пустым, а объект будет удалён через deleter, который хранит unique_ptr. Это не то же самое, что release.

Когда другая функция принимает unique_ptr, передайте владение явно:
consume(std::move(ptr));


get берёт указатель на время, reset удаляет объект, release отдаёт ответственность вам.

📣 C++ Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3🔥3
Проверяем числовой аргумент командной строки в C++!

Допустим, утилита принимает номер порта. Преобразовать строку в число мало, нужно отклонить пустой ввод, лишние символы и значение вне допустимого диапазона. Иначе строка вроде 8080abc может превратиться в 8080.

Возьмём строку из аргументов программы и подготовим границы для разбора:
std::string_view input = argv[1];
const char* first = input.data();
const char* last = first + input.size();


Перед чтением argv[1] проверь, что argc равен 2. Пустую строку тоже лучше сразу отклонить, чтобы сообщение об ошибке было понятным.

Теперь вызовем std::from_chars. Он не создаёт временную строку и сообщает результат через код ошибки:
int port = 0;
auto [next, error] = std::from_chars(first, last, port);


Одного успешного преобразования недостаточно. Проверим, что функция действительно дошла до конца входа:
if (error != std::errc{} || next != last) {
std::cerr << "Нужен целый номер порта\n";
return 1;
}


Так мы отсекаем не только буквы после числа, но и переполнение int. В отличие от некоторых удобных преобразований, from_chars не пропускает пробелы в начале строки автоматически.

Осталось проверить предметное правило. Для TCP и UDP допустимы номера от 1 до 65535:
if (port < 1 || port > 65535) {
std::cerr << "Порт вне диапазона\n";
return 1;
}


Теперь утилита принимает только полное корректное число в нужном диапазоне. Этот же приём подходит для размеров буфера, таймаутов и числовых параметров конфигурации.

📣 C++ Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍5🔥2🤝1
Шпаргалка по std::variant в C++!

На картинке показано, как один объект хранит значение одного из нескольких заранее известных типов. Видно, что у variant есть активная альтернатива, её индекс и место под самый большой из перечисленных типов.

Сохрани, чтобы не потерять!

📣 C++ Ready | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14❤10👍4
Почему std::accumulate может потерять дробную часть?

Допустим, в векторе лежат измерения с дробной частью:
std::vector<double> v{1.5, 2.5};


Сумму хочется получить одним вызовом, поэтому пишем:
auto sum = std::accumulate(v.begin(), v.end(), 0);


По логики должно получиться 4.0. Но последний аргумент здесь не просто стартовое значение. Его тип определяет тип аккумулятора и результата. Целочисленный 0 заставляет std::accumulate складывать числа в int.

Проверим результат:
std::cout << sum;


На экране будет 3. После прибавления 1.5 аккумулятор станет 1, а после прибавления 2.5 станет 3. Дробная часть отбрасывается на каждом шаге, а не только в конце.

Для дробной суммы достаточно начать с double:
auto sum = std::accumulate(v.begin(), v.end(), 0.0);


Теперь получится 4.0. Если результат сразу присвоить переменной double, но оставить внутри вызова 0, это не поможет:
double sum = std::accumulate(v.begin(), v.end(), 0);


К моменту присваивания дробные части уже потеряны. Для больших целочисленных сумм действует та же логика, начальное значение типа 0LL позволяет считать в long long и снижает риск переполнения int.

📣 C++ Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤7👍4
Как не дать медленной нейросети превратить прямой эфир в просмотр прошлого?

RTSP-конвейер принимает видео с камеры и передаёт кадры в YOLO для распознавания объектов.

В статье разбирают
• почему очередь кадров увеличивает задержку
• как ограничить накопление и обрабатывать свежие кадры
• как C++20 помогает управлять потоками

Главная идея в том, что для видеонаблюдения актуальный кадр иногда важнее обработки каждого кадра подряд.

Продолжай читать на Habr!


📣 C++ Ready | #статья
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8❤5👍4
Шпаргалка по интерфейсу std::string!

На картинке собраны основные части std::string: создание и изменение строки, доступ к символам, поиск, сравнение и преобразование. Это компактная карта стандартного типа, к которой удобно возвращаться при работе с текстом.

Сохрани, чтобы не потерять!

📣 C++ Ready | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14🔥9👍4😁1
Почему std::scoped_lock удобнее, когда нужно захватить несколько mutex?

В многопоточном коде иногда нужно изменить два объекта сразу. Например, перевести деньги между аккаунтами, перенести задачу из одной очереди в другую или обновить две связанные структуры данных.

Наивный вариант выглядит просто:
from.mutex.lock();
to.mutex.lock();

from.balance -= amount;
to.balance += amount;

to.mutex.unlock();
from.mutex.unlock();


Но у такого кода сразу несколько проблем. Если между lock и unlock произойдёт исключение, mutex может остаться заблокированным. А если другой поток захватит те же mutex в обратном порядке, можно получить deadlock.

Чуть лучше использовать lock_guard:
std::lock_guard first(from.mutex);
std::lock_guard second(to.mutex);


Но порядок всё равно остаётся важным. Если в другом месте кода сначала захватят to.mutex, а потом from.mutex, риск взаимной блокировки никуда не исчезает.

Для таких случаев в C++17 есть std::scoped_lock:
std::scoped_lock lock(from.mutex, to.mutex);

from.balance -= amount;
to.balance += amount;


std::scoped_lock умеет захватывать несколько mutex сразу и использует алгоритм, который помогает избежать deadlock при одновременном захвате. Освобождение произойдёт автоматически при выходе из области видимости.

Это особенно удобно в функциях, где есть ранний return:
std::scoped_lock lock(a.mutex, b.mutex);

if (a.closed || b.closed) {
return false;
}


Код становится короче, а главное, намерение читается сразу. Мы не вручную управляем блокировками, а объявляем область, внутри которой защищены сразу несколько ресурсов.

📣 C++ Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥6👍4
Считаем частоту ошибок в логах на C++!

Представим, что у нас есть строки логов, и нужно быстро понять, какие типы ошибок встречаются чаще всего. Для этого удобно пройтись по строкам, вытащить код ошибки и накопить счётчик в unordered_map.

Начнём с набора строк:
std::vector<std::string> logs = {
"ERROR DB_TIMEOUT user=42",
"INFO request ok",
"ERROR AUTH_FAILED user=15",
"ERROR DB_TIMEOUT user=77"
};


Сделаем контейнер для статистики:
std::unordered_map<std::string, int> errors;


Теперь пройдёмся по логам и будем учитывать только строки, которые начинаются с ERROR:
for (const auto& line : logs) {
std::istringstream in(line);

std::string level;
std::string code;

in >> level >> code;

if (level == "ERROR") {
++errors[code];
}
}


Здесь operator[] удобен тем, что для нового ключа сразу создаёт значение по умолчанию. Для int это 0, поэтому ++errors[code] работает без отдельной проверки.

Выведем результат:
for (const auto& [code, count] : errors) {
std::cout << code << " " << count << "\\n";
}


На выходе можно увидеть, какие ошибки повторяются чаще:
DB_TIMEOUT 2
AUTH_FAILED 1


Можно считать не только ошибки, но и HTTP-коды, имена endpoint-ов, типы событий, пользователей или любые другие категории из текстового потока.

Если порядок вывода важен, после подсчёта можно переложить пары в vector и отсортировать по count:
std::vector<std::pair<std::string, int>> top(
errors.begin(),
errors.end()
);


А затем отсортировать по убыванию:
std::ranges::sort(top, {}, [](const auto& item) {
return -item.second;
});


Получается маленькая, но полезная заготовка для анализа логов, отчётов и диагностических утилит.

📣 C++ Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤4🔥3
Ищем пять самых частых событий на C++!

В логах может быть миллионы строк, а для разбора обычно нужен короткий список повторяющихся ошибок. В этой задаче считаем события и оставляем пять лидеров с предсказуемым порядком при равной частоте.

В этой задаче:
• считаем количество каждого события
• частично сортируем кандидатов
• выводим только первые пять


Такой подход удобен для небольшого отчёта по логам и метрикам. Для очень большого числа уникальных значений отдельно продумай ограничение памяти.

📣 C++ Ready | #задача
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🔥3👍2
Почему std::string_view может быть опасным?

std::string_view удобен, когда нужно передать строку без копирования. Он хранит только указатель и размер, поэтому работает быстро.

Но важный нюанс в том, что string_view не владеет памятью. Он просто смотрит на данные, которые должны жить где-то ещё.

Опасный код может выглядеть так:
std::string_view name = make_name();

std::cout << name;


Если make_name возвращает std::string, временная строка уничтожится сразу после выражения. string_view останется смотреть в память, которая уже не принадлежит строке.

Похожая проблема бывает при возврате view из функции:
std::string_view title() {
std::string value = "ready";
return value;
}


Здесь value живёт только внутри функции. После return ссылка на его данные становится висячей.

Безопаснее возвращать owning-строку:
std::string title() {
return "ready";
}


А string_view хорошо подходит для параметров функции, когда вызывающий код владеет строкой:
void log_name(std::string_view name) {
std::cout << name;
}


Так функция не копирует строку, но и не пытается продлить её жизнь.

string_view можно хранить недолго и принимать как параметр, но нельзя использовать как замену std::string для владения данными.

📣 C++ Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🔥5👍4
Шпаргалка по основным операциям std::vector!

std::vector хранит элементы в непрерывной памяти и автоматически управляет их количеством. Карточка помогает быстро вспомнить не только названия методов, но и то, как меняется содержимое контейнера после вызова.

На картинке собраны создание и присваивание вектора, size и capacity, доступ к элементам, итераторы, вставка и удаление. Отдельно показаны reserve, push_back и emplace_back, а также разница между размером и выделенной памятью.

📣 C++ Ready | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤4🔥3
Почему operator[] у map может изменить контейнер?

В C++ у std::map и std::unordered_map индексатор выглядит как обычное чтение значения по ключу.

Например, просто проверить счётчик:
std::map<std::string, int> hits;

std::cout << hits["guest"];


Код выведет 0, но важный момент в другом. Ключ guest будет добавлен в map, потому что operator[] создаёт значение по умолчанию, если ключа ещё нет.

После такого чтения контейнер уже не пустой:
std::cout << hits.size();


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

Если нужно только проверить наличие ключа, лучше использовать contains:
if (hits.contains("guest")) {
std::cout << "found";
}


Если нужно получить значение без вставки, подходит find:
auto it = hits.find("guest");

if (it != hits.end()) {
std::cout << it->second;
}


А operator[] оставляй для случаев, когда вставка действительно ожидается:
hits["guest"]++;


В такой строке поведение как раз удобно. Если ключа не было, счётчик создастся с нулём и сразу увеличится.

Для чтения используй find или contains, для создания и обновления можно использовать operator[].

📣 C++ Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍6🔥4
Схема раздельной компиляции проекта на C++!

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

Эта схема помогает понять, почему объявление функции обычно находится в заголовке, реализация в .cpp, а ошибки вроде undefined reference возникают на этапе линковки, а не компиляции исходного файла.

Сохрани, чтобы не потерять!

📣 C++ Ready | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍3🔥1
Выучить программирование — это только половина дела.

Нужно ещё найти работу 👨‍💻

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

Поэтому хочу поделиться инструментом, который может упростить эту задачу.

Это IT-проект на базе нейросети, который помогает искать вакансии и находить возможности для трудоустройства. Вместо того чтобы тратить всё свободное время на поиск, можно воспользоваться ИИ и сосредоточиться на подготовке к собеседованиям и развитии навыков.
Please open Telegram to view this post
VIEW IN TELEGRAM
👎1
Почему результат std::clamp не всегда стоит сохранять как ссылку?

std::clamp ограничивает число снизу и сверху. Это удобно для громкости, процентов, координат и других значений с допустимым диапазоном:
int volume = std::clamp(requested, 0, 100);


Но у функции есть деталь, которую легко пропустить. Она возвращает не новое значение, а константную ссылку на один из трёх аргументов.

Когда все аргументы живут достаточно долго, ссылка может быть полезна:
int value = 120;
int low = 0;
int high = 100;
const int& limited = std::clamp(value, low, high);


Здесь limited ссылается на high. Если изменить high, изменится и значение, видимое через limited.

А вот с временными границами хранить ссылку опасно:
int value = 120;
const int& limited = std::clamp(value, 0, 100);


В этом случае выбранная граница 100 существует только до конца выражения. После точки с запятой limited становится висячей ссылкой, и читать её нельзя.

Сохранить результат по значению:
int limited = std::clamp(value, 0, 100);


Для обычных чисел копия почти ничего не стоит. А код перестаёт зависеть от времени жизни аргументов. Отдельно проверяйте, что нижняя граница не больше верхней.

📣 C++ Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍4🔥4