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.
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) |
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.
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.
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.
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.
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.
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
doneA 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; }
#endifg++ -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.
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.
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.
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"
doneEzutá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ó.
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.