Ищем команды по префиксу на C++!
В CLI-приложении пользователь может помнить только начало команды. Сделаем поиск подсказок, который быстро находит подходящие варианты в отсортированном списке.
В этой задаче:
Такой подход пригодится для автодополнения команд и названий настроек.
📣 C++ Ready | #задача
В CLI-приложении пользователь может помнить только начало команды. Сделаем поиск подсказок, который быстро находит подходящие варианты в отсортированном списке.
В этой задаче:
• сортируем команды
• находим начало через lower_bound
• собираем слова с нужным префиксом
Такой подход пригодится для автодополнения команд и названий настроек.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🔥5👍3
Знали, почему пути к файлам в C++ лучше собирать через std::filesystem::path?
Код может быстро обрастать ручными слешами, проверками расширения и поиском последнего разделителя:
Это может работать, но чем больше операций с путём, тем сложнее поддерживать строковые трюки. std::filesystem::path даёт для таких операций отдельный тип:
Оператор / соединяет части пути с учётом формата текущей платформы. Не приходится решать, какой разделитель вставлять между папками.
Части готового пути тоже можно получать без поиска символов:
Это удобно, например, когда нужно оставить только имя файла для лога или проверить расширение перед обработкой. Все три результата остаются объектами path, поэтому при выводе или передаче строковому API учитывайте требуемую кодировку.
Если в пути есть лишние точки или переходы в соседнюю папку, можно привести его к нормальной лексической форме:
Здесь получится путь data/report.txt. Но lexically_normal не обращается к файловой системе и не проверяет, существует ли файл. Это преобразование записи пути, а не проверка безопасности.
Есть и важная ловушка. Если правая часть уже является абсолютным путём, она может заменить основу:
Поэтому пользовательский путь нельзя без проверки присоединять к базовой папке, если важно остаться внутри неё. Для обычного построения путей path убирает много ручной работы.
📣 C++ Ready | #совет
Код может быстро обрастать ручными слешами, проверками расширения и поиском последнего разделителя:
std::string file = base + "/logs/app.log";
Это может работать, но чем больше операций с путём, тем сложнее поддерживать строковые трюки. std::filesystem::path даёт для таких операций отдельный тип:
namespace fs = std::filesystem;
fs::path file = fs::path(base) / "logs" / "app.log";
Оператор / соединяет части пути с учётом формата текущей платформы. Не приходится решать, какой разделитель вставлять между папками.
Части готового пути тоже можно получать без поиска символов:
auto name = file.filename();
auto folder = file.parent_path();
auto extension = file.extension();
Это удобно, например, когда нужно оставить только имя файла для лога или проверить расширение перед обработкой. Все три результата остаются объектами path, поэтому при выводе или передаче строковому API учитывайте требуемую кодировку.
Если в пути есть лишние точки или переходы в соседнюю папку, можно привести его к нормальной лексической форме:
fs::path clean = (fs::path("data") / "temp" / ".." / "report.txt")
.lexically_normal();Здесь получится путь data/report.txt. Но lexically_normal не обращается к файловой системе и не проверяет, существует ли файл. Это преобразование записи пути, а не проверка безопасности.
Есть и важная ловушка. Если правая часть уже является абсолютным путём, она может заменить основу:
fs::path result = fs::path("/srv/app") / "/tmp/log.txt";Поэтому пользовательский путь нельзя без проверки присоединять к базовой папке, если важно остаться внутри неё. Для обычного построения путей path убирает много ручной работы.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍4🔥4
Можно ли собирать обычные C++-приложения в браузере?
Автор описывает систему, где прямо в браузере работает набор инструментов для сборки нативных приложений под разные платформы.
В статье разбирают:
• зачем понадобился единый слой над платформенными библиотеками
• как clang и lld удалось запустить через WebAssembly
• чем заменили привычные вызовы fork и внешних утилит
• как из браузера собирается и запускается приложение с графикой
Это история о реальных ограничениях кроссплатформенной сборки и о том.
📣 C++ Ready | #статья
Автор описывает систему, где прямо в браузере работает набор инструментов для сборки нативных приложений под разные платформы.
В статье разбирают:
• зачем понадобился единый слой над платформенными библиотеками
• как clang и lld удалось запустить через WebAssembly
• чем заменили привычные вызовы fork и внешних утилит
• как из браузера собирается и запускается приложение с графикой
Это история о реальных ограничениях кроссплатформенной сборки и о том.
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👍3🔥3👎2
Почему unique_ptr::release() может оставить объект в памяти навсегда?
Обычно unique_ptr удаляет объект, когда владелец выходит из области видимости. Это одна из причин, почему его удобно возвращать из функций и хранить в классе:
Но у unique_ptr есть метод release. Название звучит так, будто он освобождает память:
На самом деле release освобождает unique_ptr от владения объектом. Он возвращает обычный указатель и оставляет ответственность за удаление вызывающему коду. Если результат проигнорировать, объект больше никто не удалит.
Когда нужен только временный доступ без передачи владения, используйте get:
ptr по-прежнему владеет объектом. Сохранить borrowed после уничтожения ptr нельзя, зато вручную удалять его тоже не нужно.
Если объект нужно уничтожить прямо сейчас, подойдет reset:
После reset указатель станет пустым, а объект будет удалён через deleter, который хранит unique_ptr. Это не то же самое, что release.
Когда другая функция принимает unique_ptr, передайте владение явно:
get берёт указатель на время, reset удаляет объект, release отдаёт ответственность вам.
📣 C++ Ready | #совет
Обычно 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 отдаёт ответственность вам.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3🔥3
Проверяем числовой аргумент командной строки в C++!
Допустим, утилита принимает номер порта. Преобразовать строку в число мало, нужно отклонить пустой ввод, лишние символы и значение вне допустимого диапазона. Иначе строка вроде 8080abc может превратиться в 8080.
Возьмём строку из аргументов программы и подготовим границы для разбора:
Перед чтением argv[1] проверь, что argc равен 2. Пустую строку тоже лучше сразу отклонить, чтобы сообщение об ошибке было понятным.
Теперь вызовем std::from_chars. Он не создаёт временную строку и сообщает результат через код ошибки:
Одного успешного преобразования недостаточно. Проверим, что функция действительно дошла до конца входа:
Так мы отсекаем не только буквы после числа, но и переполнение int. В отличие от некоторых удобных преобразований, from_chars не пропускает пробелы в начале строки автоматически.
Осталось проверить предметное правило. Для TCP и UDP допустимы номера от 1 до 65535:
Теперь утилита принимает только полное корректное число в нужном диапазоне. Этот же приём подходит для размеров буфера, таймаутов и числовых параметров конфигурации.
📣 C++ Ready | #практика
Допустим, утилита принимает номер порта. Преобразовать строку в число мало, нужно отклонить пустой ввод, лишние символы и значение вне допустимого диапазона. Иначе строка вроде 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;
}Теперь утилита принимает только полное корректное число в нужном диапазоне. Этот же приём подходит для размеров буфера, таймаутов и числовых параметров конфигурации.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍5🔥2🤝1
Почему std::accumulate может потерять дробную часть?
Допустим, в векторе лежат измерения с дробной частью:
Сумму хочется получить одним вызовом, поэтому пишем:
По логики должно получиться 4.0. Но последний аргумент здесь не просто стартовое значение. Его тип определяет тип аккумулятора и результата. Целочисленный 0 заставляет std::accumulate складывать числа в int.
Проверим результат:
На экране будет 3. После прибавления 1.5 аккумулятор станет 1, а после прибавления 2.5 станет 3. Дробная часть отбрасывается на каждом шаге, а не только в конце.
Для дробной суммы достаточно начать с double:
Теперь получится 4.0. Если результат сразу присвоить переменной double, но оставить внутри вызова 0, это не поможет:
К моменту присваивания дробные части уже потеряны. Для больших целочисленных сумм действует та же логика, начальное значение типа 0LL позволяет считать в long long и снижает риск переполнения int.
📣 C++ Ready | #совет
Допустим, в векторе лежат измерения с дробной частью:
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.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤7👍4
Как не дать медленной нейросети превратить прямой эфир в просмотр прошлого?
RTSP-конвейер принимает видео с камеры и передаёт кадры в YOLO для распознавания объектов.
В статье разбирают
• почему очередь кадров увеличивает задержку
• как ограничить накопление и обрабатывать свежие кадры
• как C++20 помогает управлять потоками
Главная идея в том, что для видеонаблюдения актуальный кадр иногда важнее обработки каждого кадра подряд.
📣 C++ Ready | #статья
RTSP-конвейер принимает видео с камеры и передаёт кадры в YOLO для распознавания объектов.
В статье разбирают
• почему очередь кадров увеличивает задержку
• как ограничить накопление и обрабатывать свежие кадры
• как C++20 помогает управлять потоками
Главная идея в том, что для видеонаблюдения актуальный кадр иногда важнее обработки каждого кадра подряд.
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8❤5👍4
Шпаргалка по интерфейсу std::string!
На картинке собраны основные части std::string: создание и изменение строки, доступ к символам, поиск, сравнение и преобразование. Это компактная карта стандартного типа, к которой удобно возвращаться при работе с текстом.
Сохрани, чтобы не потерять!
📣 C++ Ready | #ресурс
На картинке собраны основные части std::string: создание и изменение строки, доступ к символам, поиск, сравнение и преобразование. Это компактная карта стандартного типа, к которой удобно возвращаться при работе с текстом.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14🔥9👍4😁1
Почему std::scoped_lock удобнее, когда нужно захватить несколько mutex?
В многопоточном коде иногда нужно изменить два объекта сразу. Например, перевести деньги между аккаунтами, перенести задачу из одной очереди в другую или обновить две связанные структуры данных.
Наивный вариант выглядит просто:
Но у такого кода сразу несколько проблем. Если между lock и unlock произойдёт исключение, mutex может остаться заблокированным. А если другой поток захватит те же mutex в обратном порядке, можно получить deadlock.
Чуть лучше использовать lock_guard:
Но порядок всё равно остаётся важным. Если в другом месте кода сначала захватят to.mutex, а потом from.mutex, риск взаимной блокировки никуда не исчезает.
Для таких случаев в C++17 есть std::scoped_lock:
std::scoped_lock умеет захватывать несколько mutex сразу и использует алгоритм, который помогает избежать deadlock при одновременном захвате. Освобождение произойдёт автоматически при выходе из области видимости.
Это особенно удобно в функциях, где есть ранний return:
Код становится короче, а главное, намерение читается сразу. Мы не вручную управляем блокировками, а объявляем область, внутри которой защищены сразу несколько ресурсов.
📣 C++ Ready | #совет
В многопоточном коде иногда нужно изменить два объекта сразу. Например, перевести деньги между аккаунтами, перенести задачу из одной очереди в другую или обновить две связанные структуры данных.
Наивный вариант выглядит просто:
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;
}
Код становится короче, а главное, намерение читается сразу. Мы не вручную управляем блокировками, а объявляем область, внутри которой защищены сразу несколько ресурсов.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥6👍4
Считаем частоту ошибок в логах на C++!
Представим, что у нас есть строки логов, и нужно быстро понять, какие типы ошибок встречаются чаще всего. Для этого удобно пройтись по строкам, вытащить код ошибки и накопить счётчик в unordered_map.
Начнём с набора строк:
Сделаем контейнер для статистики:
Теперь пройдёмся по логам и будем учитывать только строки, которые начинаются с ERROR:
Здесь operator[] удобен тем, что для нового ключа сразу создаёт значение по умолчанию. Для int это 0, поэтому ++errors[code] работает без отдельной проверки.
Выведем результат:
На выходе можно увидеть, какие ошибки повторяются чаще:
Можно считать не только ошибки, но и HTTP-коды, имена endpoint-ов, типы событий, пользователей или любые другие категории из текстового потока.
Если порядок вывода важен, после подсчёта можно переложить пары в vector и отсортировать по count:
А затем отсортировать по убыванию:
Получается маленькая, но полезная заготовка для анализа логов, отчётов и диагностических утилит.
📣 C++ Ready | #практика
Представим, что у нас есть строки логов, и нужно быстро понять, какие типы ошибок встречаются чаще всего. Для этого удобно пройтись по строкам, вытащить код ошибки и накопить счётчик в 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;
});Получается маленькая, но полезная заготовка для анализа логов, отчётов и диагностических утилит.
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