45.2 C++26 könyvtári és konkurencia-kitekintés

Ez a rész szándékosan kitekintés, nem laboranyag. A könyv szerződése szerint a laborprofil alapja az ISO C++20, a C++23-at külön jelöltük, a C++26 pedig nem része a fordítási és tesztelési mátrixnak. A cél itt kettős: egyrészt fel kell ismerni, hogy a korábbi fejezetekben kézzel megépített megoldások melyik szabványos elem előképének tekinthetők, másrészt meg kell tanulni óvatosan olvasni a „C++26-ban lesz” típusú állításokat. Ez utóbbi nem formai kitétel: a szabvány státusza és a fordítók támogatása két külön dolog, és a generálás pillanatában ebben a szövegben nem történt forrásellenőrzés, így az alábbiakban minden konkrét papírszám és képesség mellett jelezzük, hogy az adott állítás ellenőrzése a kiválasztott forrásból még hátra van. A papírszámok a fejezettervből származó azonosítók; önmagukban nem bizonyítják sem az elfogadást, sem az implementáltságot.

45.2.1 Hogyan olvassuk a státuszjelöléseket

Négy dolgot érdemes külön tartani. A papír azonosítója (például P2300) a javaslatot nevezi meg, nem annak állapotát. Az elfogadás azt jelenti, hogy a bizottság beemelte a következő szabványtervezetbe; erről a WG21 jegyzőkönyvei és a nyilvános munkatervezet ad felvilágosítást. Az implementáltság azt jelenti, hogy egy konkrét compiler- és library-kiadásban a képesség elérhető; ezt feature-test makróval vagy compile-probe-bal lehet ellenőrizni, és verzióhoz kötött. A stabilitás pedig azt, hogy a könyvtár viselkedése nem változik-e visszamenőleg. A négy státusz nem esik egybe: egy elfogadott papír lehet, hogy egyik általunk használt fordítóban sem elérhető, és egy kísérleti implementáció létezhet már az elfogadás előtt is. Ezért az alábbi táblázat a két szintet külön oszlopban, dátummal adja meg.

A bizottsági oszlop forrása: a papírok a megadott revízióval; a C++26-ba kerülésüket a cppreference C++26-oldalának 2026-10-25-i állapota alapján rögzítettük (másodlagos forrás), a C++26 lezárásának tényét pedig a forrásjegyzék [C++26] tétele. Az implementációs oszlop helyi mérés: a lab-gcc profil GCC 13.3.0 fordítója nem ismeri a -std=c++2c módot, és -std=c++2b mellett a megfelelő feature-test makrók (__cpp_lib_senders, __cpp_lib_function_ref, __cpp_lib_copyable_function, __cpp_lib_hive, __cpp_lib_inplace_vector, __cpp_lib_hazard_pointer, __cpp_lib_rcu) nincsenek definiálva. Más fordítók támogatását a saját profiljukban, feature-test makróval kell mérni.

Korábbi labormegoldás C++26 irány Papír Bizottsági státusz (2026-10-25) lab-gcc
coroutine-alapú task (Aszinkron I/O adapter és coroutine-labor (35.5)) std::execution sender/receiver P2300R10 C++26 része; technikailag lezárt, ISO-kiadás nem igazolt nem támogatott
move_only_function (Callable és forwarding eszközök: move_only_function, forward_like, bind_back (43.3)) function_ref, copyable_function P0792R14, P2548R6 C++26 része nem támogatott
flat_map/flat_set (flat_map és flat_set: rendezett tárolás folytonos memóriában (43.1)) hive, inplace_vector P0447R28, P0843R14 C++26 része nem támogatott
shared_ptr és visszanyerés (shared ownership, reclamation és konkurens review (34.6)) hazard pointer, RCU P2530R3, P2545R4 C++26 része nem támogatott
kézi bounds-ellenőrzés hardened standard library P3471R4 C++26 része nem támogatott (nincs C++26 mód)

45.2.2 A konkurencia: sender/receiver és a coroutine-task viszonya

A korábbi coroutine-laborban (Aszinkron I/O adapter és coroutine-labor (35.5)) egy task<T>-t építettünk, amely co_await-tel felfüggeszti a hívót, a promise_type tárolja az eredményt vagy a kivételt, és a coroutine_handle élettartama a hívó felelőssége. Ez a modell akkor kényelmes, ha a futtatás egy szálhoz és egy végrehajtóhoz kötött, és a felfüggesztési pontokat kézzel jelöljük ki.

A std::execution (P2300) egy ennél általánosabb, deklaratív modellt javasol. A sender egy value-szemantikájú, csak mozgatható műveletleíró, amely deklarálja, milyen befejezési csatornái vannak (sikerérték, hiba, leállítás). A receiver fogadja ezeket a befejezéseket. A kettő összekapcsolásából jön létre az operation state, amely egyetlen, egyszer elindítható aszinkron műveletet reprezentál, és amelynek élettartama a befejezésig tart. A szerződés kulcselemei: a sender és a receiver csak mozgatható, a befejezés pontosan egyszer történik meg, és a befejezés után a receiver nem hívható újra. A co_await ebben a modellben nem az egyetlen kompozíciós eszköz: a sender-ek then, when_all, let_value jellegű adapterekkel gráffá fűzhetők, és a coroutine-task egy ilyen gráf csomópontjaként is megjelenhet.

A gyakorlati következtetés a mi szintünkön az, hogy a kézi task nem elavult, hanem egy szűkebb szerződés: egy szálon, egy végrehajtón, explicit felfüggesztési pontokkal. Ha a C++26-os modell elérhetővé válik a laborprofilban, a task belső implementációja és a hívási felület is változhat, de a tanult élettartamkérdések — ki birtokolja a coroutine_handle-t, meddig él a frame, mi történik a megszakításkor — változatlanul relevánsak maradnak.

45.2.3 Tárolók és callable-ek: a korábbi saját megoldások helyettesítése

A flat_map/flat_set (flat_map és flat_set: rendezett tárolás folytonos memóriában (43.1)) folytonos memóriában tartja a kulcs-érték párokat, és a beszúrás átrendezéssel jár. A hive egy másik kompromisszum: bucketekbe szervezett, rendezetlen tároló, amelynek fő ígérete, hogy a beszúrás nem invalidálja a többi elemre mutató pointert és referenciát, a törlés pedig csak a törölt elemre hat. Ez pont az a tulajdonság, amiért a std::vector-alapú megoldásnál stable indexeket kellett kézzel fenntartanunk. Az inplace_vector<T, N> fix kapacitású, de dinamikus méretű tároló, amelynek elemei magában az objektumban foglalnak helyet; nem allokál, így a „van-e elég hely” kérdés a beszúrás előfeltételévé válik, és a túlcsordulás kezelése az API választásától függ — ezt a részletet a konkrét könyvtár dokumentációjából kell ellenőrizni.

A callable-eknél a move_only_function (Callable és forwarding eszközök: move_only_function, forward_like, bind_back (43.3)) után a function_ref a nem tulajdonló, olcsón másolható nézetet jelöli: nem tartja életben a hívott objektumot, ezért a hívott callable élettartamának a használat teljes idejét fednie kell. A copyable_function ezzel szemben értékszemantikájú, másolható típus-törlést ad. A korábbi laborban mindkettőt kézzel modelleztük; a C++26-os elemek ezeket a szerződéseket emelik standard szintre, de a lifetime-szabály nem enyhül: a function_ref ugyanúgy danglingolhat, mint a saját nézetünk.

45.2.4 Adatpárhuzamosság: simd, linalg, submdspan

A std::simd data-parallel típusokat javasol: egy érték több lane-t hordoz, a műveletek elemenként hatnak, és a lane-szám implementációfüggő. Ez nem helyettesíti a szálakat, hanem a szálon belüli adatszintű párhuzamosságot teszi hordozhatóvá — pont azt a kézi, compiler-intrinsic alapú vektorizálást, amit a teljesítménylaborban (Optimalizálási labor correctness kapukkal (37.6)) csak méréssel mertünk volna állítani. A std::linalg BLAS-szerződésű lineáris algebrai műveleteket ad, a submdspan pedig a C++23-as mdspan (mdspan mélyebben: layoutok, accessorok és C-interop (43.2)) szeletelését egészíti ki. Mindhárom esetben a numerikus pontosság, a lane-szám és a mögöttes hardver nem része a hordozhatósági ígéretnek; ezeket targetprofilhoz kell kötni, ahogy eddig is tettük.

45.2.5 Visszanyerés: hazard pointer és RCU

A shared_ptr konkurens használata (shared ownership, reclamation és konkurens review (34.6)) atomikus referenciaszámlálással működik, ami olvasási oldalon is írási forgalmat generál a közös kontrol blokkra. A hazard pointer (P2530) ezt úgy kerüli el, hogy minden olvasó egy thread-local listában publikálja, mely csomópontot használja éppen; a felszabadító csak akkor szabadítja fel a csomópontot, ha egyik publikált hazard pointer sem mutat rá. Az RCU (P2545) ennél is tovább megy: az olvasók lock nélkül dolgoznak egy verzióval, az írók új verziót publikálnak, a régi felszabadítása pedig egy grace period után történik, amikor már biztos, hogy nincs olvasó azon a verzión. Mindkettő lock-free struktúrák visszanyerési mintája; a helyesség feltétele a publikálás és a visszanyerés közötti szinkronizációs rend, amelyet a korábbi happens-before fejezet (Data race, happens-before és publikalás (15.2)) szerint kell indokolni. A standard library ezekből várhatóan eszközöket ad, nem kész adatszerkezeteket.

45.2.6 Hardened library és kisebb kiegészítések

A hardened standard library (P3471) irányelve, hogy a standard könyvtár előfeltétel-sértéseit — például egy vector::operator[] tartományon kívüli indexelését — diagnosztizálhatóvá tegye, ahelyett hogy csendben nem definiált viselkedésbe futnánk. Ez nem azonos a fordító -D_GLIBCXX_ASSERTIONS kapcsolójával, és nem garantálja, hogy minden implementáció ugyanazt a viselkedést választja; a lényeg, hogy a könyvtár saját szerződése explicit legyen. A telítő aritmetika a túlcsordulást a típus szélső értékén megállító műveleteket ad — hasznos jelfeldolgozásban és bizonytalan bemenetű összegzésnél, de nem helyettesíti a C-ben tanult overflow-ellenőrzést (Promóciók, signedness és túlcsordulás (4.2)). A std::text_encoding kódlap-azonosítót ad (IANA-név és MIB-szám), amellyel a korábbi locale-kezelést (Szövegkódolás, locale és UTF-8 C-ben (7.5), Szövegkódolás, locale és random (32.5)) lehet pontosítani. A <debugging> header breakpoint() és is_debugger_present() jellegű eszközöket javasol, amelyek a debugger-labor (Debugger, core dump és optimalizált program (16.1)) kézi trükkjeit standardizálnák.

45.2.7 Kipróbálás a gyakorlatban

A kipróbálás első lépése nem az, hogy C++26-os kódot írunk, hanem hogy megmérjük, mit tud a rendelkezésre álló eszköz. A fordítási mód támogatása önmagában nem bizonyítja a könyvtár támogatását:

for std in c++20 c++23 c++26 c++2c; do
  printf '%-6s ' "$std"
  if g++ -std=$std -x c++ -fsyntax-only /dev/null 2>/dev/null; then
    echo "mód elfogadva"
  else
    echo "mód nem támogatott"
  fi
done

A könyvtári elemeket feature-test makróval vagy közvetlen include-próbával érdemes ellenőrizni. Az alábbi példa szándékosan nem állít semmit a támogatásról; csak a mérés mintáját mutatja, és a makrónév a választott könyvtár dokumentációjából ellenőrizendő:

// probe_inplace_vector.cpp — a fordítás sikeressége a mérés, nem a program futása
#include <version>

#if defined(__cpp_lib_inplace_vector)
#  include <inplace_vector>
int main() { std::inplace_vector<int, 4> v; v.push_back(1); return 0; }
#else
int main() { return 1; }
#endif
g++ -std=c++20 -O2 -Wall -Wextra -pedantic -o demo_fixed_queue demo_fixed_queue.cpp
clang++ -std=c++26 -stdlib=libc++ -fsyntax-only probe_inplace_vector.cpp   # a támogatás verziófüggő

A lenti C++20-as példa azt a két mintát közelíti, amelyeket a C++26 standardizálna: egy fix kapacitású, nem allokáló sor és egy nem tulajdonló callable-nézet. A kódot ebben a szövegben nem fordítottuk le és nem futtattuk; a várt viselkedés leírás, nem mért eredmény.

// demo_fixed_queue.cpp — C++20, saját közelítés; nem tesztelt
#include <array>
#include <cstddef>
#include <cstdio>
#include <optional>
#include <utility>

template <class T, std::size_t N>
class fixed_queue {
public:
    static constexpr std::size_t capacity = N;

    bool push(T v) {
        if (size_ == N) return false;
        data_[tail_] = v;
        tail_ = (tail_ + 1) % N;
        ++size_;
        return true;
    }

    std::optional<T> pop() {
        if (size_ == 0) return std::nullopt;
        T v = data_[head_];
        head_ = (head_ + 1) % N;
        --size_;
        return v;
    }

    std::size_t size() const { return size_; }

private:
    std::array<T, N> data_{};
    std::size_t head_ = 0, tail_ = 0, size_ = 0;
};

template <class F>
int apply_twice(F& f, int x) { return f(f(x)); }  // nem tulajdonol: F& élettartama a hívóé

int main() {
    fixed_queue<int, 4> q;
    std::printf("capacity=%zu\n", fixed_queue<int, 4>::capacity);
    for (int i = 0; i < 6; ++i) {
        std::printf("push(%d) -> %d\n", i, q.push(i) ? 1 : 0);
    }
    while (auto v = q.pop()) std::printf("pop %d\n", *v);

    int base = 10;
    auto add_base = [base](int v) { return v + base; };
    std::printf("apply_twice=%d\n", apply_twice(add_base, 1));
}

Az apply_twice szándékosan referenciát vesz át: ez a function_ref szerződésének lényege, hogy a hívott objektum élettartama a hívó felelőssége. Ha a lambdát ideiglenesként adnánk át, a hívás után dangling referenciánk maradna.

45.2.8 Tipikus hibák

A leggyakoribb hiba, hogy valaki a fejezettervben vagy egy hírportálon látott papírszámból arra következtet, hogy a képesség „már a C++26-ban van”, és éles kódba teszi. Ez két okból hibás: az elfogadás és az implementáltság nem ugyanaz, és a laborprofilunk C++20. A második hiba a -std=c++26 és a -std=c++2c összekeverése: a fordítók a kiadás előtt gyakran a munkanevet fogadják el, és a két kapcsoló viselkedése kiadásonként változhat. A harmadik, hogy a function_ref-et tulajdonló típusnak használják, és a hívott lambdát ideiglenesként adják át. A negyedik, hogy a hive pointerstabilitását kihasználva egy pointert a tároló megszűnése után is használnak — a stabilitás a tároló élettartamán belül értendő. Végül tipikus hiba a hardened módot biztonsági garanciának tekinteni: az előfeltétel-sértés diagnosztizálása nem azt jelenti, hogy a program helyes.

45.2.9 Feladatok

1. feladat (magyarázó). Rendeld hozzá a négy előfeltétel-részben (shared ownership, reclamation és konkurens review (34.6), Aszinkron I/O adapter és coroutine-labor (35.5), flat_map és flat_set: rendezett tárolás folytonos memóriában (43.1), Callable és forwarding eszközök: move_only_function, forward_like, bind_back (43.3)) épített megoldásokat a fejezetben szereplő C++26-os irányokhoz, és mindegyiknél indokold, mi a nyereség és mi a megmaradó kockázat.

2. feladat (kódolási). Egészítsd ki a fixed_queue példát egy pop_all művelettel, amely egy nem tulajdonló callable-n keresztül dolgozza fel az elemeket. Fogalmazd meg a callable élettartamára vonatkozó előfeltételt, és mutasd meg, hogy a tároló nem allokál.

3. feladat (diagnosztikai). Készíts olyan mérési jegyzőkönyvet, amely legalább három C++26-os könyvtári elem elérhetőségét vizsgálja a rendelkezésre álló fordítóval, és rögzíti a compiler- és library-verziót.

45.2.10 Megoldások

1. megoldás. A coroutine-task (Aszinkron I/O adapter és coroutine-labor (35.5)) a std::execution sender/receiverhez kapcsolódik: a nyereség a kompozíció és a befejezési csatornák deklaratív kezelése, a kockázat a hosszabb élettartam-lánc (operation state) és a megszakítás szemantikájának újbóli megtanulása. A move_only_function (Callable és forwarding eszközök: move_only_function, forward_like, bind_back (43.3)) a function_ref/copyable_function irányhoz: a nyereség a nem tulajdonló hívás olcsó átadása, a kockázat a dangling, amelyet a típus nem akadályoz meg. A flat_map/flat_set (flat_map és flat_set: rendezett tárolás folytonos memóriában (43.1)) a hive/inplace_vector irányhoz: a nyereség a pointerstabilitás, illetve az allokációmentesség, a kockázat a fix kapacitás miatti előfeltétel és a rendezetlenség. A shared_ptr (shared ownership, reclamation és konkurens review (34.6)) a hazard pointer/RCU irányhoz: a nyereség az olvasási oldal írásmentessége, a kockázat a grace period és a publikálás helyes indoklása. Mindegyiknél közös, hogy a státuszt a konkrét implementációból kell ellenőrizni.

2. megoldás. A pop_all sablonparaméterként referenciát vegyen át, ne értéket:

template <class F>
void pop_all(F& sink) {
    while (auto v = pop()) sink(*v);
}

Az előfeltétel: a sink élettartama fedje a pop_all teljes futását, és a sink ne tegye tönkre a fixed_queue-t (például ne hívjon push-t rekurzívan). Az allokációmentesség ellenőrzési feltétele: a fixed_queue tagjai között csak std::array és skalárok vannak, a push/pop/pop_all nem hív allokáló függvényt; ezt kódolvasással és — a laborban — allokációfigyelő eszközzel lehet igazolni, nem e szöveg állításaként.

3. megoldás. A jegyzőkönyv mintája:

g++ --version | head -n1
clang++ --version | head -n1
for std in c++20 c++23 c++26 c++2c; do
  g++ -std=$std -x c++ -fsyntax-only /dev/null 2>/dev/null \
    && echo "$std: mód elfogadva" || echo "$std: mód nem támogatott"
done

Ezután elemenként külön próbafájlt kell fordítani, és rögzíteni kell a feature-test makró jelenlétét vagy hiányát. Az értékelés szabálya: a „mód elfogadva” csak a nyelvi módra vonatkozik, a könyvtári elemre nem; a hiányzó makró nem bizonyítja, hogy a képesség nincs a szabványban, és a jelenlévő makró nem bizonyítja, hogy a viselkedés stabil. A jegyzőkönyvbe a dátumot és a pontos verziókat is fel kell venni, különben a mérés nem reprodukálható.

45.2.11 Összefoglaló

A C++26 könyvtári irányai jól olvashatók a könyv korábbi laborjainak folytatásaként: a std::execution a coroutine-task általánosítása, a function_ref/copyable_function a kézi callable-nézet standardizálása, a hive/inplace_vector a folytonos és stabil tárolás más kompromisszumai, a simd/linalg/submdspan az adatpárhuzamos és többdimenziós számítás eszközei, a hazard pointer és az RCU a visszanyerés lock-free mintái, a hardened library pedig a könyvtári előfeltételek diagnosztizálásának iránya. Egyik sem része a laborprofilnak, és egyik állítás sem tekinthető ellenőrzöttnek addig, amíg a konkrét papír státuszát, a compiler- és library-verziót, valamint a feature-test makrót nem ellenőriztük. A helyes munkamód az, amit a könyv eddig is követett: először mérni, aztán állítani — és a mérést dátummal, verzióval és a korlátok kimondásával rögzíteni.