Close Menu
    Ciekawe

    Jak podłączyć telefon do monitora? Przewodowe i bezprzewodowe sposoby

    2025-12-08

    Co można wrzucić w koszty firmy jednoosobowej? Lista i praktyczne przykłady

    2025-12-03

    Jak podłączyć okulary VR do PS4? Poradnik podłączenia i konfiguracji

    2025-12-02
    Facebook X (Twitter) Instagram
    CPP Polska
    Facebook X (Twitter) Instagram
    • Biznes

      Co można wrzucić w koszty firmy jednoosobowej? Lista i praktyczne przykłady

      2025-12-03

      Jak zapobiec wyciekom danych firmowych?

      2025-11-28

      Ile kosztuje prowadzenie jednoosobowej działalności gospodarczej? Przegląd opłat

      2025-11-10

      Jak wziąć samochód w leasing bez firmy? Poradnik dla osób fizycznych

      2025-10-29

      Jak założyć firmę jednoosobową krok po kroku – koszty, formalności i czas trwania

      2025-10-23
    • Technologie

      Jak podłączyć telefon do monitora? Przewodowe i bezprzewodowe sposoby

      2025-12-08

      Jak podłączyć okulary VR do PS4? Poradnik podłączenia i konfiguracji

      2025-12-02

      Jak zapobiec wyciekom danych firmowych?

      2025-11-28

      Jak sprawdzić rozdzielczość monitora w Windows i macOS?

      2025-11-26

      Jak zresetować laptopa Acer do ustawień fabrycznych? Poradnik krok po kroku

      2025-11-25
    • Programowanie

      Maszyna stanów oparta o std::variant

      2025-10-07

      Tablice w C++ od podstaw – deklaracja, inicjalizacja, iteracja i typowe pułapki

      2025-10-07

      std::deque w C++ – kiedy wybrać dwukierunkową kolejkę zamiast vectora

      2025-10-07

      itoa i std::to_chars – konwersja liczb na tekst bez narzutu wydajności

      2025-10-07

      strcpy vs strncpy vs std::string – bezpieczne kopiowanie łańcuchów w C++

      2025-10-07
    • Inne

      Jak prowadzić blog programistyczny i dzielić się wiedzą?

      2025-06-28
    CPP Polska
    Home»C++»Przewodnik po coroutines w C++
    C++

    Przewodnik po coroutines w C++

    Oskar KlimkiewiczBy Oskar KlimkiewiczUpdated:2025-06-28Brak komentarzy4 Mins Read
    Share Facebook Twitter LinkedIn Email Copy Link
    Follow Us
    RSS
    black and blue computer keyboard
    Share
    Facebook Twitter LinkedIn Email Copy Link

    Kompleksowy przewodnik po współprogramach C++ – od podstaw do zaawansowanej implementacji

    C++20 wprowadził współprogramy jako przełomową funkcję umożliwiającą kooperatywne wielozadaniowość, programowanie asynchroniczne i leniwą obliczalność. W przeciwieństwie do tradycyjnych funkcji, współprogramy mogą zawieszać wykonanie w określonych punktach (co_await, co_yield, co_return) i później je wznawiać, zachowując stan pomiędzy wywołaniami. Niniejszy artykuł omawia mechanikę współprogramów, wzorce implementacyjne oraz praktyczne zastosowania, bazując na standardzie C++, transformacjach kompilatora i realnych scenariuszach użycia.

    1. Wprowadzenie do współprogramów

    Współprogramy generalizują podprogramy poprzez umożliwienie zawieszania i wznawiania wykonania. Umożliwia to:

    • Przechowywanie stanu pomiędzy wywołaniami.
    • Asynchroniczne wejście/wyjście bez blokowania wątków.
    • Leniwą ewaluację (np. generatory).
      C++ współprogramy są bezkstosowe – punkty zawieszenia przechowują stan na stercie (rama współprogramu), a nie na stosie wywołań. Funkcja staje się współprogramem, jeśli zawiera co_await, co_yield lub co_return i zwraca typ spełniający wymogi współprogramu.

    2. Mechanika działania współprogramów i transformacje kompilatora

    Kiedy współprogram jest wywoływany, kompilator:

    1. Alokuje ramę współprogramu przechowującą zmienne lokalne, parametry i stan zawieszenia.
    2. Tworzy obiekt typu promise (za pomocą std::coroutine_traits) do zarządzania zachowaniem współprogramu.
    3. Generuje logikę maszyny stanów odpowiedzialną za zawieszanie i wznawianie.

    2.1 Struktura ramy współprogramu

    Rama przechowuje:

    • Obiekt promise,
    • Zawieszony kontekst wykonania (np. rejestry),
    • Zmienne lokalne i tymczasowe.
      Alokacja pamięci jest konfigurowalna przez operator new lub std::allocator_arg.

    2.2 Odpowiedzialności typu promise

    Typ promise definiuje:

    • get_return_object() – Tworzy wartość zwracaną przez współprogram;
    • initial_suspend() / final_suspend() – Kontrolują zawieszenie na początku lub na końcu;
    • unhandled_exception() – Obsługuje wyjątki;
    • return_void() / return_value() – Obsługuje co_return.
    struct GeneratorPromise {
        std::suspend_always initial_suspend() noexcept { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        Generator get_return_object() { /* … */ }
        void return_void() noexcept {}
        // …
    };
    

    3. Słowa kluczowe współprogramów i punkty rozszerzalności

    3.1 co_await i protokół awaitable

    co_await expr zawiesza wykonanie, jeśli awaitable expr nie jest gotowy. Obiekt awaitable musi definiować:

    • await_ready() – Zwraca false, jeśli potrzebne jest zawieszenie,
    • await_suspend(coroutine_handle) – Wznawia, gdy gotowe,
    • await_resume() – Zwraca wynik.
    struct Awaitable {
        bool await_ready();
        void await_suspend(std::coroutine_handle<>);
        int await_resume();
    };
    

    Standard dostarcza std::suspend_always (zawsze zawiesza) oraz std::suspend_never (nigdy nie zawiesza).

    3.2 co_yield dla generatorów

    co_yield value to cukier składniowy dla:

    co_await promise.yield_value(value);
    

    Zawiesza współprogram i zwraca value do wywołującego.

    3.3 co_return do zakończenia

    co_return wywołuje promise.return_void() lub promise.return_value() przed finalnym zawieszeniem.

    4. Budowa generatora – współprogram generujący

    Generator produkuje wartości leniwie. Poniżej przykład generatora ciągu Fibonacciego:

    4.1 Typ promise i typ zwracany

    template<typename T>
    struct Generator {
        struct promise_type {
            T current_value;
            Generator get_return_object() { return Generator(this); }
            std::suspend_always initial_suspend() noexcept { return {}; }
            std::suspend_always final_suspend() noexcept { return {}; }
            std::suspend_always yield_value(T value) {
                current_value = value;
                return {};
            }
            void return_void() noexcept {}
        };
        // Zarządzanie handle współprogramu
        explicit Generator(promise_type* p)
            : handle(std::coroutine_handle::from_promise(*p)) {}
        bool next() {
            if (handle.done()) return false;
            handle.resume();
            return !handle.done();
        }
        T value() const { return handle.promise().current_value; }
    private:
        std::coroutine_handle handle;
    };
    

    4.2 Ciało współprogramu

    Generator<int> fib() {
        int a = 0, b = 1;
        while (true) {
            co_yield a;
            auto next = a + b;
            a = b;
            b = next;
        }
    }
    // Użycie:
    auto gen = fib();
    gen.next(); // 0
    gen.value(); // 0
    gen.next(); // 1
    

    5. Awaitable dla operacji asynchronicznych

    Obiekty awaitable łączą współprogramy z operacjami asynchronicznymi. Przykład: integracja odczytu pliku:

    5.1 Własny awaitable odczytu pliku

    struct FileReadAwaitable {
        int fd;
        std::vector<uint8_t> buffer;
    
        bool await_ready() { /* Sprawdź czy dane gotowe */ }
        void await_suspend(std::coroutine_handle<> h) {
            // Zarejestruj callback do wznowienia 'h', gdy I/O zakończy się
        }
        std::tuple<std::error_code, size_t> await_resume() {
            return {error_code, bytes_read};
        }
    };
    
    task readFile() {
        auto [ec, bytes] = co_await FileReadAwaitable{fd, buffer};
        // Przetwarzaj dane
    }
    

    6. Zaawansowane tematy

    6.1 Optymalizacja pamięci

    • Eliminacja współprogramu – Kompilatory mogą unikać alokacji na stercie dla współprogramów o statycznie znanym czasie życia;
    • Własne alokatory – Możliwa nadpisanie operator new w typie promise.

    6.2 Propagacja wyjątków

    promise_type::unhandled_exception() powinien przechwytywać wyjątki do ponownego zgłoszenia przez coroutine_handle.

    6.3 Wielowątkowość i współprogramy

    Współprogramy wznawiane są na wątku wywołującym/wznawiającym, co umożliwia migrację wątków bez blokad.

    7. Nowości w C++23

    C++23 upraszcza korzystanie ze współprogramów:

    • std::generator<T> – Standardowy leniwy generator (np. dla ranges);
    • std::ranges::elements_of – Umożliwia generatory rekurencyjne.
    std::generator<int> tree_traversal(Node* node) {
        if (node) {
            co_yield node->value;
            co_yield std::ranges::elements_of(tree_traversal(node->left));
            co_yield std::ranges::elements_of(tree_traversal(node->right));
        }
    }
    

    8. Dobre praktyki i pułapki

    • Unikaj parametrów przez referencję – Parametry przekazane przez referencję mogą odwoływać się do nieistniejących obiektów, jeśli współprogram przetrwa funkcję wywołującą;
    • Zarządzanie zasobami – Używaj RAII lub final_suspend(), by zapobiegać wyciekom pamięci;
    • Wsparcie kompilatora – Używaj GCC z -fcoroutines lub Clang z -fcoroutines-ts oraz libc++.

    Podsumowanie

    Współprogramy w C++ są podstawowym mechanizmem do programowania asynchronicznego, generatorów i obliczeń ze stanem. Choć początkowo wymagają większej ilości kodu szablonowego, zrozumienie współpracy promise, awaitable i coroutine handle pozwala na budowę potężnych abstrakcji. Przyszłe standardy rozszerzą wsparcie biblioteczne (np. networking), upraszczając implementację. Wdrażając współprogramy w systemach produkcyjnych, należy priorytetowo traktować bezpieczeństwo pamięciowe, obsługę wyjątków oraz kompatybilność z kompilatorem.

    Zagadnienia opracowano na podstawie materiałów KDAB, analizy Lewisa Bakera, Modernes C++ oraz dokumentacji standardu C++.

    Polecane:

    • Praktyczne użycie std::optional w nowoczesnym C++
    • Wzorzec PImpl
    • Przegląd języka C++ – co nowego w standardach od C++11 do C++23
    • Zaawansowane scenariusze z std::visit i wieloma wariantami
    • Podstawy pracy z Google Mock – kurs krok po kroku
    Share. Facebook Twitter LinkedIn Email Copy Link
    Oskar Klimkiewicz
    • Website

    Inżynier oprogramowania specjalizujący się w C++, absolwent Wydziału Elektroniki i Technik Informacyjnych Politechniki Warszawskiej. Od ponad 8 lat projektuje i rozwija systemy o wysokiej dostępności, głównie dla branży fintech i IoT. PS. Zdjęcie wyretuszowane przez AI :)

    Podobne artykuły

    Maszyna stanów oparta o std::variant

    8 Mins Read

    Tablice w C++ od podstaw – deklaracja, inicjalizacja, iteracja i typowe pułapki

    4 Mins Read

    std::deque w C++ – kiedy wybrać dwukierunkową kolejkę zamiast vectora

    4 Mins Read
    Leave A Reply Cancel Reply

    Oglądaj, słuchaj, ćwicz - zdobywaj nowe umiejętności online
    Nie przegap

    Jak podłączyć telefon do monitora? Przewodowe i bezprzewodowe sposoby

    Oskar Klimkiewicz6 Mins Read

    Podłączenie telefonu do monitora to jedna z najistotniejszych innowacji ery mobilnej, umożliwiająca przeniesienie doświadczeń z…

    Co można wrzucić w koszty firmy jednoosobowej? Lista i praktyczne przykłady

    2025-12-03

    Jak podłączyć okulary VR do PS4? Poradnik podłączenia i konfiguracji

    2025-12-02

    Jak zapobiec wyciekom danych firmowych?

    2025-11-28
    Social media
    • Facebook
    • Twitter
    • LinkedIn
    O nas
    O nas

    CPP Polska to serwis internetowy poświęcony technologii, programowaniu, IT, biznesowi i finansom. Znajdziesz tu porady, wskazówki i instrukcje dla wszystkich czytelników IT & Tech & Biz.

    Facebook X (Twitter) LinkedIn RSS
    Najnowsze

    Jak podłączyć telefon do monitora? Przewodowe i bezprzewodowe sposoby

    2025-12-08

    Co można wrzucić w koszty firmy jednoosobowej? Lista i praktyczne przykłady

    2025-12-03

    Jak podłączyć okulary VR do PS4? Poradnik podłączenia i konfiguracji

    2025-12-02
    Popularne

    Skrajnie niepotrzebne, skrajne przypadki w C++

    2025-06-28

    Wyszukiwanie testów w Google Test – metody i narzędzia

    2025-06-28

    Czy C jest wolniejszy od C++? Zero-cost abstraction w praktyce

    2025-06-28
    © 2025 CPP Polska. Wszelkie prawa zastrzeżone.
    • Lista publikacji
    • Współpraca
    • Kontakt

    Type above and press Enter to search. Press Esc to cancel.