Ищем пять самых частых событий на 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 не владеет памятью. Он просто смотрит на данные, которые должны жить где-то ещё.
Опасный код может выглядеть так:
Если make_name возвращает std::string, временная строка уничтожится сразу после выражения. string_view останется смотреть в память, которая уже не принадлежит строке.
Похожая проблема бывает при возврате view из функции:
Здесь value живёт только внутри функции. После return ссылка на его данные становится висячей.
Безопаснее возвращать owning-строку:
А string_view хорошо подходит для параметров функции, когда вызывающий код владеет строкой:
Так функция не копирует строку, но и не пытается продлить её жизнь.
string_view можно хранить недолго и принимать как параметр, но нельзя использовать как замену std::string для владения данными.
📣 C++ Ready | #совет
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 для владения данными.
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 | #ресурс
std::vector хранит элементы в непрерывной памяти и автоматически управляет их количеством. Карточка помогает быстро вспомнить не только названия методов, но и то, как меняется содержимое контейнера после вызова.
На картинке собраны создание и присваивание вектора, size и capacity, доступ к элементам, итераторы, вставка и удаление. Отдельно показаны reserve, push_back и emplace_back, а также разница между размером и выделенной памятью.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤4🔥3
Почему operator[] у map может изменить контейнер?
В C++ у std::map и std::unordered_map индексатор выглядит как обычное чтение значения по ключу.
Например, просто проверить счётчик:
Код выведет 0, но важный момент в другом. Ключ guest будет добавлен в map, потому что operator[] создаёт значение по умолчанию, если ключа ещё нет.
После такого чтения контейнер уже не пустой:
Это особенно неприятно в коде, где map используется как кеш, статистика, настройки или таблица пользователей. Можно случайно создать запись, которой в данных на самом деле не было.
Если нужно только проверить наличие ключа, лучше использовать contains:
Если нужно получить значение без вставки, подходит find:
А operator[] оставляй для случаев, когда вставка действительно ожидается:
В такой строке поведение как раз удобно. Если ключа не было, счётчик создастся с нулём и сразу увеличится.
Для чтения используй find или contains, для создания и обновления можно использовать operator[].
📣 C++ Ready | #совет
В 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[].
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍6🔥4
Схема раздельной компиляции проекта на C++!
На изображении показан путь от исходников и заголовков через препроцессор и компиляцию отдельных файлов к объектным файлам, которые линковщик соединяет в программу.
Эта схема помогает понять, почему объявление функции обычно находится в заголовке, реализация в .cpp, а ошибки вроде undefined reference возникают на этапе линковки, а не компиляции исходного файла.
Сохрани, чтобы не потерять!
📣 C++ Ready | #ресурс
На изображении показан путь от исходников и заголовков через препроцессор и компиляцию отдельных файлов к объектным файлам, которые линковщик соединяет в программу.
Эта схема помогает понять, почему объявление функции обычно находится в заголовке, реализация в .cpp, а ошибки вроде undefined reference возникают на этапе линковки, а не компиляции исходного файла.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍3🔥1
Почему результат std::clamp не всегда стоит сохранять как ссылку?
std::clamp ограничивает число снизу и сверху. Это удобно для громкости, процентов, координат и других значений с допустимым диапазоном:
Но у функции есть деталь, которую легко пропустить. Она возвращает не новое значение, а константную ссылку на один из трёх аргументов.
Когда все аргументы живут достаточно долго, ссылка может быть полезна:
Здесь limited ссылается на high. Если изменить high, изменится и значение, видимое через limited.
А вот с временными границами хранить ссылку опасно:
В этом случае выбранная граница 100 существует только до конца выражения. После точки с запятой limited становится висячей ссылкой, и читать её нельзя.
Сохранить результат по значению:
Для обычных чисел копия почти ничего не стоит. А код перестаёт зависеть от времени жизни аргументов. Отдельно проверяйте, что нижняя граница не больше верхней.
📣 C++ Ready | #совет
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);
Для обычных чисел копия почти ничего не стоит. А код перестаёт зависеть от времени жизни аргументов. Отдельно проверяйте, что нижняя граница не больше верхней.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍4🔥4
Зачем сегодня выбирать C++ и где его возможности действительно нужны?
В интервью с C++-разработчиком обсуждают не только язык, но и инженерную работу вокруг него. Разговор опирается на опыт высоконагруженных сервисов, разработки фреймворков и поиска ошибок, которые могут проявиться уже под реальной нагрузкой.
В статье разбирают:
• почему контроль над памятью даёт производительность и повышает цену ошибок
• как санитайзеры и профилировщики помогают искать проблемы до продакшена
• где C++ оправдан, а где проще выбрать другой язык и масштабировать систему
Язык выбирают под ограничения и задачи системы, а сильная инженерная база важнее привязки к одному синтаксису.
📣 C++ Ready | #ресурс
В интервью с C++-разработчиком обсуждают не только язык, но и инженерную работу вокруг него. Разговор опирается на опыт высоконагруженных сервисов, разработки фреймворков и поиска ошибок, которые могут проявиться уже под реальной нагрузкой.
В статье разбирают:
• почему контроль над памятью даёт производительность и повышает цену ошибок
• как санитайзеры и профилировщики помогают искать проблемы до продакшена
• где C++ оправдан, а где проще выбрать другой язык и масштабировать систему
Язык выбирают под ограничения и задачи системы, а сильная инженерная база важнее привязки к одному синтаксису.
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍3🔥2
Почему std::move иногда всё равно копирует объект?
std::move сам ничего не переносит, а только приводит выражение к rvalue-ссылке, чтобы компилятор мог выбрать подходящий конструктор.
Посмотри на тип объекта до и после вызова:
candidate имеет тип const std::string&&. Константность не исчезает, потому что std::move сохраняет квалификаторы исходного объекта.
Обычный move-конструктор принимает неконстантную rvalue-ссылку:
Он может изменить исходную строку, забрав её внутренний буфер. С const std::string&& это невозможно, поэтому такой конструктор не подходит.
Для константного источника компилятор обычно выбирает копирующий вариант:
В результате код остаётся корректным, но большой буфер копируется. Сам факт, что написан std::move, не гарантирует перемещение.
Если владение можно передать, оставь источник неконстантным:
После операции source остаётся валидным объектом, но его значение не определено для прикладной логики. Его можно присвоить заново или уничтожить, но не стоит полагаться на прежнее содержимое.
При возврате локального объекта std::move обычно тоже не нужен:
Компилятор может применить NRVO и создать результат сразу в месте вызова. Принудительный std::move в return способен помешать этой оптимизации и не делает код быстрее автоматически.
📣 C++ Ready | #совет
std::move сам ничего не переносит, а только приводит выражение к rvalue-ссылке, чтобы компилятор мог выбрать подходящий конструктор.
Посмотри на тип объекта до и после вызова:
const std::string source = "payload";
auto&& candidate = std::move(source);
candidate имеет тип const std::string&&. Константность не исчезает, потому что std::move сохраняет квалификаторы исходного объекта.
Обычный move-конструктор принимает неконстантную rvalue-ссылку:
std::string(std::string&& other);
Он может изменить исходную строку, забрав её внутренний буфер. С const std::string&& это невозможно, поэтому такой конструктор не подходит.
Для константного источника компилятор обычно выбирает копирующий вариант:
std::string::string(const std::string& other);
В результате код остаётся корректным, но большой буфер копируется. Сам факт, что написан std::move, не гарантирует перемещение.
Если владение можно передать, оставь источник неконстантным:
std::string source = "payload";
std::string result = std::move(source);
После операции source остаётся валидным объектом, но его значение не определено для прикладной логики. Его можно присвоить заново или уничтожить, но не стоит полагаться на прежнее содержимое.
При возврате локального объекта std::move обычно тоже не нужен:
std::string make_message() {
std::string result = "ready";
return result;
}Компилятор может применить NRVO и создать результат сразу в месте вызова. Принудительный std::move в return способен помешать этой оптимизации и не делает код быстрее автоматически.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍5🔥3
Шпаргалка по пределам числовых типов в C++!
std::numeric_limits<T> помогает узнать свойства типа прямо из кода. Для целых типов можно проверить границы, а для float и double наглядно сравнить точность и специальные значения.
На картинке показаны signed и unsigned типы, диапазоны float и double, а также lowest, min, max, epsilon и infinity.
Сохрани, чтобы не потерять!
📣 C++ Ready | #ресурс
std::numeric_limits<T> помогает узнать свойства типа прямо из кода. Для целых типов можно проверить границы, а для float и double наглядно сравнить точность и специальные значения.
На картинке показаны signed и unsigned типы, диапазоны float и double, а также lowest, min, max, epsilon и infinity.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍4🔥3
Группируем метрики по минутным окнам на C++!
Представим, что сервис записывает latency каждого запроса. Для графика не всегда нужны все отдельные измерения, поэтому соберём число событий и среднее значение для каждой минуты.
Храним у каждого измерения время и значение, а в сводке накапливаем сумму и количество:
Ключом сделаем начало минуты. floor округляет время вниз, поэтому измерения в 10:14:02 и 10:14:59 попадут в одно окно:
Для каждого измерения вычисляем ключ и обновляем аккумулятор:
Здесь хранится сумма, а не готовое среднее. Так обновление занимает постоянное время и не требует пересчитывать все предыдущие значения окна.
После чтения входных данных проходим по окнам в хронологическом порядке и считаем среднее:
На выходе каждая строка содержит начало минуты, количество запросов и среднюю задержку.
📣 C++ Ready | #практика
Представим, что сервис записывает latency каждого запроса. Для графика не всегда нужны все отдельные измерения, поэтому соберём число событий и среднее значение для каждой минуты.
Храним у каждого измерения время и значение, а в сводке накапливаем сумму и количество:
struct Sample { std::chrono::system_clock::time_point at; double value; };
struct Stats { double total{}; std::size_t count{}; };Ключом сделаем начало минуты. floor округляет время вниз, поэтому измерения в 10:14:02 и 10:14:59 попадут в одно окно:
using Minute = std::chrono::time_point<
std::chrono::system_clock, std::chrono::minutes>;
std::map<Minute, Stats> buckets;
Для каждого измерения вычисляем ключ и обновляем аккумулятор:
auto minute = std::chrono::floor<std::chrono::minutes>(sample.at);
auto& stats = buckets[minute];
stats.total += sample.value;
++stats.count;
Здесь хранится сумма, а не готовое среднее. Так обновление занимает постоянное время и не требует пересчитывать все предыдущие значения окна.
После чтения входных данных проходим по окнам в хронологическом порядке и считаем среднее:
for (const auto& [minute, stats] : buckets) {
const double average = stats.total / stats.count;
writeRow(minute, stats.count, average);
}На выходе каждая строка содержит начало минуты, количество запросов и среднюю задержку.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3🔥2
Объединяем отсортированные логи на C++!
Несколько файлов уже содержат записи в порядке времени, но для анализа нужен один общий поток. Соберём его через min-heap, читая из каждого файла только текущую запись.
В этой задаче:
Так объём дополнительной памяти зависит от числа файлов, а не от размера логов.
📣 C++ Ready | #ресурс
Несколько файлов уже содержат записи в порядке времени, но для анализа нужен один общий поток. Соберём его через min-heap, читая из каждого файла только текущую запись.
В этой задаче:
• связываем запись с её файлом
• помещаем в очередь первые записи
• после извлечения подгружаем следующую из того же файла
Так объём дополнительной памяти зависит от числа файлов, а не от размера логов.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥3
Добавляем место вызова в сообщения об ошибках на C++!
Когда ошибка обнаруживается глубоко внутри общей функции, по одному тексту лога бывает трудно найти её источник. В C++20 std::source_location позволяет получить файл и строку вызова без ручной передачи этих параметров.
Подключаем нужные заголовки:
Добавляем source_location последним параметром со значением по умолчанию:
current() вычисляется в месте вызова функции. Поэтому в лог попадёт файл и строка кода, откуда вызвали logError, а не строка внутри самой функции.
Формируем компактную запись с координатами:
Такой формат легко искать в CI-логах и локальном выводе. При необходимости к нему добавляется имя функции через where.function_name().
Вызываем логгер:
Компилятор подставит место этого вызова автоматически.
📣 C++ Ready | #практика
Когда ошибка обнаруживается глубоко внутри общей функции, по одному тексту лога бывает трудно найти её источник. В C++20 std::source_location позволяет получить файл и строку вызова без ручной передачи этих параметров.
Подключаем нужные заголовки:
#include <iostream>
#include <source_location>
#include <string_view>
Добавляем source_location последним параметром со значением по умолчанию:
void logError(std::string_view message,
std::source_location where =
std::source_location::current()) {
current() вычисляется в месте вызова функции. Поэтому в лог попадёт файл и строка кода, откуда вызвали logError, а не строка внутри самой функции.
Формируем компактную запись с координатами:
std::cerr << where.file_name()
<< ":" << where.line()
<< " " << message << "\n";
}
Такой формат легко искать в CI-логах и локальном выводе. При необходимости к нему добавляется имя функции через where.function_name().
Вызываем логгер:
logError("config file is missing");Компилятор подставит место этого вызова автоматически.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍4🔥4
Как игровой движок распределяет тысячи корутин между потоками?
В статье разбирают shuttle_async, экспериментальный планировщик задач для Vulkan-движка ShuttleEngine. Автор показывает, как локальные очереди и work stealing помогают распределять работу между потоками.
В статье разбирают:
• как устроены локальные деки Chase-Lev и кража задач
• зачем корутинам нужен диспетчер и structured concurrency
• как устроены три режима запуска задач
• что показали замеры на малых, больших и неравномерных задачах
Результаты относятся к конкретному проекту и тестовой машине.
📣 C++ Ready | #статья
В статье разбирают shuttle_async, экспериментальный планировщик задач для Vulkan-движка ShuttleEngine. Автор показывает, как локальные очереди и work stealing помогают распределять работу между потоками.
В статье разбирают:
• как устроены локальные деки Chase-Lev и кража задач
• зачем корутинам нужен диспетчер и structured concurrency
• как устроены три режима запуска задач
• что показали замеры на малых, больших и неравномерных задачах
Результаты относятся к конкретному проекту и тестовой машине.
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍2🤝2👎1🔥1
Знаете, почему optional.value_or может выполнить лишнюю работу?
Аргументы функции вычисляются до её вызова. Поэтому fallback внутри value_or запустится даже тогда, когда optional уже содержит значение.
Например, результат загрузки может не понадобиться:
Здесь loadNameFromDisk вызывается заранее, а не только при пустом cachedName. Для чтения файла, запроса к базе или построения объекта это лишняя работа.
Если запасное значение дорого получать, проверяем optional отдельно:
Условный оператор вычисляет только выбранную ветку. Поэтому загрузка выполняется лишь при отсутствии готового значения.
Для дешёвого значения value_or остаётся коротким и удобным:
Ленивое вычисление лучше выражать условием или отдельной функцией.
📣 C++ Ready | #совет
Аргументы функции вычисляются до её вызова. Поэтому fallback внутри value_or запустится даже тогда, когда optional уже содержит значение.
Например, результат загрузки может не понадобиться:
auto name = cachedName.value_or(
loadNameFromDisk());
Здесь loadNameFromDisk вызывается заранее, а не только при пустом cachedName. Для чтения файла, запроса к базе или построения объекта это лишняя работа.
Если запасное значение дорого получать, проверяем optional отдельно:
auto name = cachedName
? *cachedName
: loadNameFromDisk();
Условный оператор вычисляет только выбранную ветку. Поэтому загрузка выполняется лишь при отсутствии готового значения.
Для дешёвого значения value_or остаётся коротким и удобным:
auto port = configuredPort.value_or(8080);
Ленивое вычисление лучше выражать условием или отдельной функцией.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤7🔥3