🧠 C++ Задача для продвинутых: безопасный счётчик с автоматическим сбросом
Условие:
Реализуйте потокобезопасный счётчик, который:
1. Увеличивается при вызове
2. Возвращает текущее значение при
3. Автоматически сбрасывается в 0 через
4. Не сбрасывается, если за это время пришёл новый
🔧 Условия:
- Нельзя использовать сторонние библиотеки (только стандарт C++17+)
- Нельзя вручную управлять потоками (используйте
- Нужно обеспечить корректный RAII (автоматическое завершение потоков при разрушении объекта)
🔍 Пример:
💡 Подсказка:
Нужно реализовать "отложенный сброс", который перезапускается при каждом increment(). Используйте фоновый поток и condition_variable.
✅ Ожидаемое решение (упрощённый скелет):
🎯 Отличная задача, чтобы потренировать:
понимание RAII
работу с condition_variable
атомарные переменные
и правильную остановку фоновых потоков
Можешь доработать под конкретные кейсы — например, логирование событий сброса или работу с несколькими счётчиками.
Условие:
Реализуйте потокобезопасный счётчик, который:
1. Увеличивается при вызове
increment()
2. Возвращает текущее значение при
get()
3. Автоматически сбрасывается в 0 через
N
миллисекунд после последнего increment()4. Не сбрасывается, если за это время пришёл новый
increment()
🔧 Условия:
- Нельзя использовать сторонние библиотеки (только стандарт C++17+)
- Нельзя вручную управлять потоками (используйте
std::thread
, std::mutex
, `std::condition_variable`)- Нужно обеспечить корректный RAII (автоматическое завершение потоков при разрушении объекта)
🔍 Пример:
SafeCounter counter(1000); // сброс через 1000 мс
counter.increment(); // value = 1
std::this_thread::sleep_for(500ms);
counter.increment(); // value = 2, таймер сброса перезапускается
std::this_thread::sleep_for(1500ms);
std::cout << counter.get(); // value = 0
💡 Подсказка:
Нужно реализовать "отложенный сброс", который перезапускается при каждом increment(). Используйте фоновый поток и condition_variable.
✅ Ожидаемое решение (упрощённый скелет):
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <chrono>
class SafeCounter {
public:
SafeCounter(int timeout_ms) : timeout(timeout_ms), value(0), stop(false) {
worker = std::thread([this] { this->watch(); });
}
~SafeCounter() {
{
std::lock_guard<std::mutex> lock(mtx);
stop = true;
}
cv.notify_all();
worker.join();
}
void increment() {
{
std::lock_guard<std::mutex> lock(mtx);
++value;
last_action = std::chrono::steady_clock::now();
}
cv.notify_all();
}
int get() const {
return value.load();
}
private:
void watch() {
std::unique_lock<std::mutex> lock(mtx);
while (!stop) {
cv.wait_for(lock, timeout, [this] {
return stop || std::chrono::steady_clock::now() - last_action >= timeout;
});
if (stop) break;
if (std::chrono::steady_clock::now() - last_action >= timeout) {
value = 0;
}
}
}
std::chrono::milliseconds timeout;
std::chrono::steady_clock::time_point last_action = std::chrono::steady_clock::now();
std::atomic<int> value;
std::mutex mtx;
std::condition_variable cv;
std::thread worker;
bool stop;
};
🎯 Отличная задача, чтобы потренировать:
понимание RAII
работу с condition_variable
атомарные переменные
и правильную остановку фоновых потоков
Можешь доработать под конкретные кейсы — например, логирование событий сброса или работу с несколькими счётчиками.
❤17👍6🔥2
🧠 Задача: Реализация `TypeList` с поддержкой операций на этапе компиляции
📌 Описание
Реализуйте шаблонный класс
1. Получение длины списка (`length`)
2. Получение типа по индексу (`at<N>`)
3. Добавление типа в начало списка (`push_front<T>`)
4. Удаление первого типа (`pop_front`)
5. Проверка наличия типа в списке (`contains<T>`)
6. Фильтрация по условию (например, только целочисленные типы) (`filter<Predicate>`)
Всё это должно работать на этапе компиляции, без использования
🧩 Пример использования
🛠 Требования к реализации
Используйте только возможности шаблонов и constexpr.
Не используйте std::tuple, std::array, if constexpr (если хотите усложнить — можно).
Предпочтительно использование C++17 или выше.
Код должен компилироваться и проходить все static_assert.
🧪 Бонусное задание
Реализуйте print_types() — функцию, которая выводит все типы из списка в std::cout (можно использовать typeid, PRETTY_FUNCTION или другие хаки).
@cpluspluc
📌 Описание
Реализуйте шаблонный класс
TypeList
, который представляет собой список типов на этапе компиляции (compile-time type list). Он должен поддерживать следующие операции:1. Получение длины списка (`length`)
2. Получение типа по индексу (`at<N>`)
3. Добавление типа в начало списка (`push_front<T>`)
4. Удаление первого типа (`pop_front`)
5. Проверка наличия типа в списке (`contains<T>`)
6. Фильтрация по условию (например, только целочисленные типы) (`filter<Predicate>`)
Всё это должно работать на этапе компиляции, без использования
std::tuple
или других runtime-контейнеров.🧩 Пример использования
#include <type_traits>
#include <iostream>
// Пример предиката
template<typename T>
struct is_integral : std::is_integral<T> {};
int main() {
using MyList = TypeList<int, char, float, double, short>;
static_assert(MyList::length == 5);
static_assert(std::is_same_v<MyList::at<0>, int>);
static_assert(std::is_same_v<MyList::at<2>, float>);
using WithBool = MyList::push_front<bool>;
static_assert(WithBool::length == 6);
static_assert(std::is_same_v<WithBool::at<0>, bool>);
using Popped = WithBool::pop_front;
static_assert(std::is_same_v<Popped, MyList>);
static_assert(MyList::contains<int>);
static_assert(!MyList::contains<bool>);
using OnlyIntegral = MyList::filter<is_integral>;
static_assert(std::is_same_v<OnlyIntegral, TypeList<int, char, short>>);
return 0;
}
🛠 Требования к реализации
Используйте только возможности шаблонов и constexpr.
Не используйте std::tuple, std::array, if constexpr (если хотите усложнить — можно).
Предпочтительно использование C++17 или выше.
Код должен компилироваться и проходить все static_assert.
🧪 Бонусное задание
Реализуйте print_types() — функцию, которая выводит все типы из списка в std::cout (можно использовать typeid, PRETTY_FUNCTION или другие хаки).
@cpluspluc
❤8🔥5👍2
🧠 C++ хитрая и интересная задача (lock-free)
Задача: реализуй однопоточный производитель / однопоточный потребитель (SPSC) кольцевой буфер без мьютексов — только на
Требования:
-
- Ёмкость — степень двойки; индексация — маской.
- Гарантируется ровно один поток-производитель и ровно один поток-потребитель.
Скелет:
💡 Подсказки:
- Пиши в buf[tail & mask], затем публикуй запись:
tail.store(next, std::memory_order_release);
- Читай из buf[head & mask] после проверки наличия данных, затем:
head.store(next, std::memory_order_release);
- На чтение границ используйте std::memory_order_acquire:
- Producer: сначала head.load(memory_order_acquire) (чтобы не переписать не потреблённое).
- Consumer: сначала tail.load(memory_order_acquire) (чтобы увидеть свежие записи).
- Ни одного fetch_add не нужно: вычисляй next = idx + 1.
- Проверка переполнения: буфер полон, если next_tail == head.
- Чтобы избежать ложного шаринга — разнеси head, tail и buf (см. alignas(64)).
- Не забудь про TriviallyCopyable/Noexcept: для общего T лучше использовать std::is_nothrow_copy_assignable_v<T> и/или перемещение.
🎯 Бонус-тест
В двух потоках гоняй счётчик 0..1e7 через буфер.
На выходе проверь, что последовательность непрерывна и без пропусков.
Задача: реализуй однопоточный производитель / однопоточный потребитель (SPSC) кольцевой буфер без мьютексов — только на
std::atomic
и правильных порядках памяти.Требования:
-
push(const T&)
и pop(T&)
— O(1)
, без блокировок; возвращают false
, если буфер полон/пуст.- Ёмкость — степень двойки; индексация — маской.
- Гарантируется ровно один поток-производитель и ровно один поток-потребитель.
Скелет:
#include <atomic>
#include <array>
#include <cstddef>
#include <optional>
template<typename T, std::size_t N>
class SpscRing {
static_assert((N & (N - 1)) == 0, "N must be power of two");
public:
bool push(const T& v) {
// твой код
return false;
}
bool pop(T& out) {
// твой код
return false;
}
private:
alignas(64) std::atomic<size_t> head{0}; // consumer reads
alignas(64) std::atomic<size_t> tail{0}; // producer writes
alignas(64) std::array<T, N> buf{};
static constexpr size_t mask = N - 1;
};
💡 Подсказки:
- Пиши в buf[tail & mask], затем публикуй запись:
tail.store(next, std::memory_order_release);
- Читай из buf[head & mask] после проверки наличия данных, затем:
head.store(next, std::memory_order_release);
- На чтение границ используйте std::memory_order_acquire:
- Producer: сначала head.load(memory_order_acquire) (чтобы не переписать не потреблённое).
- Consumer: сначала tail.load(memory_order_acquire) (чтобы увидеть свежие записи).
- Ни одного fetch_add не нужно: вычисляй next = idx + 1.
- Проверка переполнения: буфер полон, если next_tail == head.
- Чтобы избежать ложного шаринга — разнеси head, tail и buf (см. alignas(64)).
- Не забудь про TriviallyCopyable/Noexcept: для общего T лучше использовать std::is_nothrow_copy_assignable_v<T> и/или перемещение.
🎯 Бонус-тест
В двух потоках гоняй счётчик 0..1e7 через буфер.
На выходе проверь, что последовательность непрерывна и без пропусков.
❤6🔥3🥰1