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

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

Реклама на бирже: https://telega.in/channels/cpp_ready/card

РКН: https://clck.ru/3QREHc
Download Telegram
Объединяем отсортированные логи на C++!

Несколько файлов уже содержат записи в порядке времени, но для анализа нужен один общий поток. Соберём его через min-heap, читая из каждого файла только текущую запись.

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


Так объём дополнительной памяти зависит от числа файлов, а не от размера логов.

📣 C++ Ready | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥3
Добавляем место вызова в сообщения об ошибках на C++!

Когда ошибка обнаруживается глубоко внутри общей функции, по одному тексту лога бывает трудно найти её источник. В 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");


Компилятор подставит место этого вызова автоматически.

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

В статье разбирают shuttle_async, экспериментальный планировщик задач для Vulkan-движка ShuttleEngine. Автор показывает, как локальные очереди и work stealing помогают распределять работу между потоками.

В статье разбирают:
• как устроены локальные деки Chase-Lev и кража задач
• зачем корутинам нужен диспетчер и structured concurrency
• как устроены три режима запуска задач
• что показали замеры на малых, больших и неравномерных задачах

Результаты относятся к конкретному проекту и тестовой машине.

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


📣 C++ Ready | #статья
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍2🤝2👎1🔥1
Знаете, почему optional.value_or может выполнить лишнюю работу?

Аргументы функции вычисляются до её вызова. Поэтому 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);


Ленивое вычисление лучше выражать условием или отдельной функцией.

📣 C++ Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤7🔥3
Объединяем пересекающиеся интервалы на C++!

Дан список занятых временных интервалов. Нужно объединить пересекающиеся и соприкасающиеся отрезки.

Представляем каждый интервал парой начала и конца. Считаем границы полуоткрытыми, поэтому отрезки [2, 5) и [5, 7) тоже объединяются:
#include <algorithm>
#include <utility>
#include <vector>

using Interval = std::pair<int, int>;
std::vector<Interval> busy{
    {1, 3}, {2, 5}, {8, 10}, {10, 12}
};


Сначала сортируем пары по началу, а при равенстве по концу. После сортировки каждый следующий интервал можно сравнить только с последним результатом:
std::sort(busy.begin(), busy.end());
std::vector<Interval> merged;


Если новый интервал начинается позже конца последнего, добавляем его отдельно. Иначе расширяем конец уже собранного интервала до максимальной границы:
for (const auto& current : busy) {
    if (merged.empty() ||
        merged.back().second < current.first) {
        merged.push_back(current);
    } else {
        merged.back().second = std::max(
            merged.back().second, current.second);
    }
}


Строгое сравнение оставляет соприкасающиеся интервалы в одной группе. Для полуоткрытых границ результат примера будет таким:
[1, 5) [8, 12)


После сортировки алгоритм делает один проход и работает за O(n log n), где основное время уходит на сортировку.

📣 C++ Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍4🔥3
Порядок создания объекта в C++!

Схема показывает, что память под объект выделяется до запуска конструктора. Затем создаются поля класса, и только после этого выполняется тело конструктора.

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

📣 C++ Ready | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
❤15👍7🔥3
Схема протокола acquire-release в C++!

Release-store и acquire-load помогают одному потоку опубликовать подготовленные данные, а другому безопасно увидеть их после сигнала готовности. Синхронизация возникает, когда acquire-load получает значение от release-store того же atomic-объекта.

На картинке показаны оба потока, общий флаг x и порядок операций до и после передачи сигнала. Подпись отдельно подчёркивает важное условие. Обе операции должны использовать один и тот же atomic-флаг.

📣 C++ Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤3🔥3🤝1
Потолок зарплаты в IT — ВСЁ

Даже Senior-разработчики упираются в потолок. Дальше — менеджмент, смена компании или годы ожидания повышения.

Но есть другой путь — зарабатывать больше на уже накопленном IT-опыте. И для этого необязательно переучиваться с нуля.

Как именно, рассказывает Артём Алехин — экс-тимлид Сбера, работавший на валютных удаленках.
Он совмещал российский найм с валютным контрактом, зарабатывая $8–9 тысяч в месяц. Артем создал проект в рамках которого получил для своих 200 учеников 40+ офферов на 200к+.

Три направления, с которыми работает Артём:
— Валютная удалёнка: выход на зарубежный рынок и поиск офферов от $5 000.
— Монетизация экспертизы: превращать свой IT-опыт в доход через обучение и менторство
— Свой IT-продукт: запуск SaaS с проверкой спроса сервиса для селлеров.

Артём помогает выбрать подходящий путь и выстроить стратегию роста дохода, чтобы перестать зависеть от одного работодателя.

Переходи в канал Артёма и изучай: @artem_itishnaya
👎9😁1
Знали, что ключ в std::map можно переименовать без пересоздания узла?

Ключи внутри map нельзя менять напрямую, потому что контейнер поддерживает порядок элементов. Начиная с C++17 элемент можно временно извлечь как node handle, изменить его ключ и вставить обратно без копирования хранимого значения.

Сначала извлекаем элемент и проверяем, что он найден:
auto node = users.extract(oldKey);
if (node.empty()) return;


Пустой node handle означает, что такого ключа в контейнере не было. Пока узел извлечён, он не участвует в сортировке map, поэтому ключ можно изменить:
auto oldKey = node.key();
node.key() = newKey;


Сам объект и его mapped value остаются внутри узла. Это полезно, когда значение дорого копировать или оно не копируется вовсе:
auto& profile = node.mapped();
profile.lastSeen = now;


Теперь пробуем вернуть узел и проверяем результат, потому что newKey мог уже появиться в map:
auto result = users.insert(std::move(node));
if (result.inserted) {
    std::cout << "renamed\n";
}


При конфликте запись не теряется, она остаётся в result.node. Можно восстановить старый ключ и вернуть её в контейнер:
if (!result.inserted) {
    result.node.key() = oldKey;
    users.insert(std::move(result.node));
}


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

📣 C++ Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍4🔥2
Синхронизируем потоки через примитивы C++20!

В этой шпоре собраны три разных сценария координации потоков. latch ждёт однократную готовность, barrier собирает участников на каждой фазе, а counting_semaphore ограничивает параллельный доступ к ресурсу.

📣 C++ Ready | #шпора
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12❤4👍3
Please open Telegram to view this post
VIEW IN TELEGRAM
😁6👎4
Почему компаратор с <= может сломать std::sort?

При сортировке чисел по возрастанию хочется написать сравнение так:
std::sort(values.begin(), values.end(),
[](int a, int b) { return a <= b; });


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

Проверим самую простую ситуацию:
auto lessOrEqual = [](int a, int b) { return a <= b; };
std::cout << lessOrEqual(5, 5);


Результат равен 1. Но std::sort требует строгий слабый порядок, элемент не может быть меньше самого себя. Использование такого компаратора нарушает требования алгоритма.

Правильный вариант для возрастания использует строгое сравнение:
std::sort(values.begin(), values.end(),
[](int a, int b) { return a < b; });


Если у объектов несколько полей, добавь запасной критерий. Например, при одинаковом приоритете можно сравнить id:
if (a.priority != b.priority) return a.priority < b.priority;
return a.id < b.id;


Так порядок будет предсказуемым и для равных приоритетов. Если же требуется сохранить исходный порядок равных элементов, используй std::stable_sort с корректным строгим компаратором, а не заменяй < на <=.

📣 C++ Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍3🔥2
Собираем конфигурацию с приоритетом источников на C++!

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

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


Так конфигурацию можно не только собрать, но и понять, какой источник переопределил параметр.

📣 C++ Ready | #задача
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤4🔥3