39.5 Záróreview és C/C++ összehasonlítás

A záróprojekt review-ja nem esztétikai ítélet arról, hogy a C++ változat „modernebb-e”, hanem annak ellenőrzése, hogy a port megtartotta-e a C capstone megfigyelhető szerződését, és ahol szándékosan eltért, ott az eltérés dokumentált, indokolt és tesztelt-e. A review három kérdésre ad választ: működik-e ugyanúgy a külvilág felé; milyen erőforrás- és élettartam-szerződést vállal a C++ kód; és milyen áron — bonyolultságban, buildidőben, futásidejű költségben — kaptuk ezt. A negyedik, gyakran elmaradó rész a nyitott kérdések őszinte felsorolása: mi az, amit a port nem bizonyított, és mi a következő ellenőrizhető lépés.

A fejezet a CMake, tesztek és mérési csomag (39.4) eredményére épül. Feltételezi, hogy a C++ core (C++ core: parser, erőforrások és értéktípusok (39.2)) és a szolgáltatás-életciklus (Task, queue és szolgáltatás-életciklus (39.3)) már elkészült, és hogy a C referencia implementáció elérhető. Nem cél a teljes termékfejlesztés vagy minden új C++ feature beépítése; a coroutine adapter például kísérleti ág marad, ha az élettartam-bizonyíték hiányos. A fejezet külső, ellenőrzött forrásra nem hivatkozik; a szabvány- és ABI-állítások a könyv korábbi, verzióhoz kötött részeire támaszkodnak, és a laborprofilban ellenőrizendők.

39.5.1 A review mint szerződésellenőrzés

A viselkedési szerződés (behavioral contract) a C referencia implementáció azon megfigyelhető tulajdonságainak halmaza, amelyekre a kliensek támaszkodhatnak: a parancssori interfész, a protokoll üzenetei, a hibakódok és hibaüzenetek, az EOF és a részleges olvasás kezelése, a leállási sorrend, valamint az erőforrások felszabadításának feltételei. A review akkor korrekt, ha ezt a szerződést a C kódból kinyerve, tételesen összeveti a C++ implementációval. Az összevetés feltétele azonos célkörnyezet: Linux/POSIX, azonos konfiguráció, rögzített compiler- és library-profil. Ha a C++ implementáció ettől eltér — például kivételt használ, ahol a C hibakódot adott —, az eltérés csak akkor fogadható el, ha az adapter a határon dokumentáltan leképezi a viselkedést.

Négy síkon érdemes review-zni. Funkcionális ekvivalencia: ugyanazokra a bemenetekre ugyanazok a megfigyelhető kimenetek és hibák. Erőforrás- és élettartam-szerződés: minden nyitott fájl, socket, allokáció és lock tulajdonosa, felszabadítási pontja és hibás ági viselkedése ismert. Költség: buildidő, bináris méret, allokációk száma, indirekciók, unwinding. Karbantarthatóság és ABI: a stabil C határ változatlan, a C++ belső típusai nem szivárognak ki, a hibakezelés nem lép át a nyelvi határon. A review eredménye nem „jó” vagy „rossz”, hanem döntések indoklása és nyitott kérdések listája.

39.5.2 C explicit cleanup versus C++ deterministic destruction

C-ben nincs nyelvi destruktor. A tipikus minta a goto cleanup: minden hibás ág egy közös címkére ugrik, ahol a korábban megszerzett erőforrások felszabadítása fordított sorrendben történik. Ennek előnye, hogy egyetlen ellenőrizhető kilépési pont van; hátránya, hogy a helyességet a programozó fegyelme és a review biztosítja. Ha egy új erőforrás-acquire vagy egy új hibás ág kimarad, a hiba csendben marad.

C++-ban a destruktor az objektum élettartamának végén, a nyelvi szabályok szerint fut le: scope kilépéskor, return, throw, goto vagy break esetén, a létrehozott objektumok fordított sorrendjében. Ez a deterministic destruction: nem szemétgyűjtés, hanem a destruktor hívásának nyelvi garanciája. Feltétele, hogy az objektum teljesen létrejött (a konstruktor befejeződött), a destruktor ne dobjon kivételt, és a move után a forrás objektum érvényes, de „moved-from” állapotban legyen. A garancia nem terjed ki arra, hogy a destruktor helyesen van megírva, és arra sem, hogy a hibát jelzi.

Ez utóbbi a legfontosabb review-pont: a close hibája. A C diag_close visszatérhet hibával, és a hívó dönthet, hogy naplózza-e. A C++ destruktor nem adhat vissza hibát; ha a close hibája számít, explicit close() metódus kell, amely std::error_code-ot ad, a destruktor pedig csak „best effort” fallback. A review-nak ezt dokumentálnia kell, különben a C++ port csendben elveszíti a C-ben még kezelt hibainformációt.

A következő két példa ugyanazt a fájlolvasó szerződést mutatja be. A C változat explicit cleanupot használ; a C++ változat RAII-t, de a close hibáját dokumentált korlátként kezeli. A kód C17, illetve C++20, Linux/POSIX célra; a laborprofilban rögzített GCC/Clang verzióval fordítható.

/* diag_c.c - C17, Linux/POSIX */
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>

struct diag_reader {
    int fd;
    unsigned char buf[4096];
    size_t len;
    size_t off;
};

static int diag_open(struct diag_reader *r, const char *path) {
    r->fd = open(path, O_RDONLY);
    if (r->fd < 0) return -1;
    r->len = 0;
    r->off = 0;
    return 0;
}

static void diag_close(struct diag_reader *r) {
    if (r->fd >= 0) {
        (void)close(r->fd);   /* a close hibaja itt nem propagalhato */
        r->fd = -1;
    }
}

/* >0: bytes, 0: EOF, -1: error */
static ssize_t diag_next(struct diag_reader *r, const unsigned char **data) {
    if (r->off == r->len) {
        ssize_t n = 0;
        do {
            n = read(r->fd, r->buf, sizeof r->buf);
        } while (n < 0 && errno == EINTR);
        if (n <= 0) return n;
        r->len = (size_t)n;
        r->off = 0;
    }
    *data = r->buf + r->off;
    size_t n = r->len - r->off;
    r->off = r->len;
    return (ssize_t)n;
}

int main(int argc, char **argv) {
    if (argc != 2) return 2;
    struct diag_reader r;
    if (diag_open(&r, argv[1]) != 0) {
        perror("open");
        return 1;
    }
    int rc = 0;
    for (;;) {
        const unsigned char *data = NULL;
        ssize_t n = diag_next(&r, &data);
        if (n < 0) { perror("read"); rc = 1; break; }
        if (n == 0) break;
        printf("%zd\n", n);
    }
    diag_close(&r);
    return rc;
}
// diag.cpp - C++20, Linux/POSIX
#include <array>
#include <cerrno>
#include <cstddef>
#include <iostream>
#include <span>
#include <system_error>
#include <fcntl.h>
#include <unistd.h>

namespace diag {

class Reader {
public:
    explicit Reader(const char* path) : fd_(::open(path, O_RDONLY)) {
        if (fd_ < 0) ec_ = std::error_code(errno, std::generic_category());
    }

    ~Reader() noexcept {
        if (fd_ >= 0) (void)::close(fd_);
    }

    Reader(const Reader&) = delete;
    Reader& operator=(const Reader&) = delete;

    Reader(Reader&& other) noexcept : fd_(other.fd_), ec_(other.ec_) {
        other.fd_ = -1;
        other.ec_.clear();
    }

    Reader& operator=(Reader&& other) noexcept {
        if (this != &other) {
            if (fd_ >= 0) (void)::close(fd_);
            fd_ = other.fd_;
            ec_ = other.ec_;
            other.fd_ = -1;
            other.ec_.clear();
        }
        return *this;
    }

    explicit operator bool() const noexcept { return fd_ >= 0; }
    std::error_code error() const noexcept { return ec_; }

    // Szerzodes: a kimeneti span a kovetkezo next() hivasig ervenyes;
    // ures span + nincs hiba = EOF; nem ures error_code = hiba.
    std::error_code next(std::span<const std::byte>& out) {
        out = {};
        if (fd_ < 0) return std::error_code(EBADF, std::generic_category());
        if (off_ == len_) {
            ssize_t n = 0;
            do {
                n = ::read(fd_, buf_.data(), buf_.size());
            } while (n < 0 && errno == EINTR);
            if (n < 0) return std::error_code(errno, std::generic_category());
            if (n == 0) return {};
            len_ = static_cast<std::size_t>(n);
            off_ = 0;
        }
        out = std::span<const std::byte>(buf_.data() + off_, len_ - off_);
        off_ = len_;
        return {};
    }

private:
    int fd_ = -1;
    std::error_code ec_{};
    std::array<std::byte, 4096> buf_{};
    std::size_t len_ = 0;
    std::size_t off_ = 0;
};

} // namespace diag

int main(int argc, char** argv) {
    if (argc != 2) return 2;
    diag::Reader reader(argv[1]);
    if (!reader) {
        std::cerr << "open: " << reader.error().message() << '\n';
        return 1;
    }
    std::span<const std::byte> chunk;
    for (;;) {
        const std::error_code ec = reader.next(chunk);
        if (ec) {
            std::cerr << "read: " << ec.message() << '\n';
            return 1;
        }
        if (chunk.empty()) break;
        std::cout << chunk.size() << '\n';
    }
    return 0;
}

A buildparancsok illusztrációk; a laborprofilban rögzített verzióval és flag-ekkel futtatandók. A fejezet nem állítja, hogy a kód itt lefutott vagy tesztelt.

gcc -std=c17 -Wall -Wextra -Wpedantic -Wconversion -g -O2 \
    -fsanitize=address,undefined diag_c.c -o diag_c
g++ -std=c++20 -Wall -Wextra -Wpedantic -Wconversion -g -O2 \
    -fsanitize=address,undefined diag.cpp -o diag_cpp

A C++ változatban a Reader a fd tulajdonosa; a destruktor csak fallback. A next spanje a Reader belső bufferére mutat: a tulajdonos a Reader, a nézet élettartama a következő next hívásig tart, az invalidáló művelet a next (felülírja a buffert) és a Reader megszűnése (dangling), a hibakimenet pedig az error_code. A C változatban a data pointer ugyanígy a diag_reader bufferére mutat, érvényes a következő diag_next-ig; a hiba -1, a részletek errno-ban. A C++ destruktor nem tudja visszaadni a close hibáját; ez dokumentált korlát, nem elhallgatott hiba.

39.5.3 Pointer+hossz versus span és borrowed lifetime

A C API-ban a tipikus átadás (const unsigned char *data, size_t len). A szerződés: data érvényes len bájtig; len == 0 esetén a pointer lehet null, de a legtöbb implementáció ezt nem követeli meg. A hívó felelős azért, hogy a pointer élettartama lefedje a használatot. A C++ std::span<const std::byte> ugyanezt a nézetet fejezi ki egy objektumban: pointer és hossz együtt utazik, a containerből implicit konverzióval előállítható, és subspan/first/last műveletekkel szeletelhető. A span nem tulajdonol, nem hosszabbítja meg a mögöttes tároló élettartamát, és nem ellenőriz boundsot: az operator[] kívülre indexelése nem definiált viselkedés, at() nincs. A subspan előfeltétele:

0≤offset≤size(),count=dynamic_extent∨offset+count≤size(). 0 \le \text{offset} \le \text{size()},\qquad \text{count} = \text{dynamic\_extent} \ \lor\ \text{offset}+\text{count} \le \text{size()}.

A span előnye, hogy a hossz nem válik el a pointertől, és a függvényszignatúra egyértelműbb. Hátránya, hogy a std::span egy C++ template típus, nem megy át a C ABI-n; a C API határán .data() és .size() lesz belőle. A review-ban minden spanhez meg kell nevezni az ownert, az érvényességi tartományt, az invalidáló műveletet és a hibakimenetet — ugyanúgy, mint a C pointer+hossz esetében. A span nem „biztonságosabb pointer”, csak jobban típusozott nézet.

A példában a Reader::next(std::span<const std::byte>&) kimeneti paramétere pontosan ezt a szerződést hordozza. A C változat const unsigned char **data paramétere ugyanazt a hosszt külön ssize_t visszatérési értékben adja. A review-nak ellenőriznie kell, hogy a hívó nem tartja-e meg a spant a következő next után, és hogy a Reader megszűnése után nem használ-e dangling nézetet. Ugyanez a hiba std::string_view-nál és minden más borrowed view-nál: a nézet visszaadása lokális std::vector-ból vagy lokális std::string-ből tipikus dangling-forrás.

39.5.4 Hibakezelés: error code, expected és exception

A C hibakezelése visszatérési kód és errno kombinációja. Az errno globális, ezért a read/open után azonnal menteni kell, mielőtt más függvény felülírja; a perror és a strerror a hiba szöveges alakját adja. A C++ három fő lehetőséget kínál. A std::error_code nem dob, olcsó, kategóriába sorolja a hibát, és a std::system_error kivételként is hordozható. A std::expected<T,E> C++23-as; értéket vagy hibát tárol, monadikus láncolást tesz lehetővé, de .value() hibás esetben dob. A kivétel unwindingot indít, a destruktorok lefutnak, de a noexcept határokon és a C ABI-n nem mehet át; a std::terminate kockázata valós.

Mechanizmus Tipikus használat Költség ABI/határ Kockázat
error_code rendszerhiba, gyakori I/O-hiba olcsó, nem allokál stabil, C-re képezhető ellenőrizetlen visszatérés
expected (C++23) típusos hiba, érték vagy hiba olcsó, de template C++ belső .value() dob, feature-test kell
exception ritka, kivételes hiba unwinding, .eh_frame C++ belső, nem C ABI noexcept megsértése, terminate

A capstone döntése indokolható: a gyakori I/O-hibákat error_code-dal vagy expected-tel kell visszaadni; a programozási hibákat assert/terminate jelzi; a kivétel csak belső, ritka hibaútvonalon marad, és az extern "C" határon try/catch-csel le kell fordítani hibakódra. A C++20-as alap miatt a példa error_code + kimeneti paraméter; a C++23-as expected ág feature-testtel kapcsolható:

#if defined(__cpp_lib_expected) && __cpp_lib_expected >= 202202L
std::expected<std::span<const std::byte>, std::error_code> next_expected();
#endif

A std::error_code message() hívása allokálhat, a what() pointer élettartama a kivételobjektumhoz kötött; ezeket a review-ban nem szabad „ingyenesnek” feltételezni. A C API határán a C++ error_code-ot dokumentáltan int-re kell képezni, és a data pointer élettartamát a C API dokumentációjában rögzíteni kell.

39.5.5 Dispatch: statikus, dinamikus és type-erased

A feldolgozó lépések kiválasztásának három fő mechanizmusa van. A dinamikus dispatch virtuális függvény: a döntés futásidőben, a vtable-en keresztül történik; egy indirekció hívásonként, nyílt típushalmaz, de a C++ ABI érzékeny rá. A statikus dispatch template, CRTP vagy concept: a döntés fordításidőben, nincs futásidejű indirekció, viszont kódduzzadás és hosszabb buildidő lehet; a típusnak a fordítási egységben láthatónak kell lennie. A type-erased dispatch (std::function, C callback + void*) futásidőben tárolja a callable-t; a std::function birtokolja és másolja a callable-t, allokálhat, a „kis objektum optimalizálás” implementációfüggő; a C callback nem birtokol, a context élettartamáért a hívó felel.

Mechanizmus Döntés ideje Futásidejű költség Bővíthetőség ABI
virtuális függvény futásidő vtable-indirekció nyílt halmaz C++ ABI
template/CRTP fordításidejű nincs indirekció, kódduzzadás zárt halmaz header
std::function futásidő indirekció, esetleg allokáció nyílt halmaz C++ ABI
C callback + void* futásidő indirekció nyílt halmaz stabil C ABI

A döntést a hívási pontok száma, a típusok halmaza, a hot path és az ABI-stabilitás együtt vezérli. Ha a lépések száma kicsi és fordításidőben ismert, a template a természetes választás; ha futásidőben konfigurált plugin-szerű lépések vannak, a type-erased forma; ha a stabil C ABI a követelmény, a C callback. A std::function mindenhol való használata a hot pathon indokolatlan allokációt és indirekciót hozhat; a review-nak méréssel vagy legalább költségbecsléssel kell alátámasztania a választást.

39.5.6 Compile-time és runtime költségmérleg

A compile-time költség a template-instantiationök száma, a header-include gráf mérete, a constexpr kiértékelés és a fordítási egységek száma. A túlzott template-használat növelheti a buildidőt és a binárist; a constexpr nem feltétlenül ingyenes, mert a fordítónak ki kell értékelnie. A runtime költség az allokációk száma, az indirekciók, a másolások, az atomi műveletek (shared_ptr refcount), az unwinding és a cache-locality. A C++ zero-overhead elve nem azt jelenti, hogy minden konstrukció ingyenes, hanem azt, hogy amit nem használunk, azért nem fizetünk; amit használunk, annak a költsége megjelenhet.

A review csak azt állíthatja mértnek, ami a CMake, tesztek és mérési csomag (39.4) mérési csomagjában rögzített, reprodukálható módszertannal szerepel: azonos bemenet, azonos compiler-flag, többszöri futás, medián és outlier-kezelés, correctness gate a benchmark előtt. A „várt” és a „mért” külön kategória. A std::function allokációja, a virtual indirekciója és a shared_ptr atomi refcountja tipikus vitapont; ezeket nem elég elméletben eldönteni, ha a capstone teljesítményérzékeny. A mérési tervet a feladatok között részletezzük; a fejezet nem állítja, hogy bármelyik változat itt megmért eredményt adott.

39.5.7 Stabil interfész, tesztbizonyíték és fennmaradó korlátok

A stabil határ a C API marad; a C++ implementáció mögötte van. Az extern "C" kikapcsolja a névtorzítást, az opaque struct elrejti a belső layoutot, a create/destroy pár azonos allocatort használ, a hibakód int, a data pointer élettartama pedig a következő hívásig tart. A C++ típusok — std::span, std::expected, std::string — nem mennek át a C ABI-n; a határon egyszerű pointerre, hosszra és hibakódra kell leképezni őket.

/* diag_api.h - stabil C hatar, C es C++ fordito is latja */
#ifndef DIAG_API_H
#define DIAG_API_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif

typedef struct diag_reader diag_reader;

diag_reader *diag_reader_create(const char *path);
void diag_reader_destroy(diag_reader *r);
/* 0 siker, -1 hiba; data a kovetkezo hivasig ervenyes */
int diag_reader_next(diag_reader *r, const unsigned char **data, size_t *len);
const char *diag_reader_error(const diag_reader *r);

#ifdef __cplusplus
}
#endif
#endif

A tesztbizonyíték akkor elég, ha ugyanaz a funkcionális teszt fut a C és a C++ implementáción, és a C API-t külön C fordítóval is lefordítjuk. Az ABI-ellenőrzés nm -D/readelf -Ws vizsgálattal, a sanitizerek (ASan/UBSan) futtatásával, negatív tesztekkel (hibás fájl, EOF, rövid olvasás, EINTR) és a hibakód-leképezés tesztelésével egészül ki. A kivételnek nem szabad átlépnie az extern "C" határon; ha a belső kód mégis dob, a határon try/catch fordítja hibakódra.

Nyitott kérdés Miért nyitott Következő lépés Elfogadási feltétel
close hiba destruktor nem adhat vissza hibát explicit close() + dokumentált fallback a hiba naplózva, a viselkedés tesztelve
EINTR/signal safety a teljes robusztus I/O ciklus külön téma a File descriptor és robusztus I/O ciklus (12.1) mintájának átemelése szignál alatti újrapróbálás tesztelve
C++23 expected compiler/library támogatás változó feature-test + C++20 fallback mindkét ág ugyanazt a viselkedést adja
coroutine adapter élettartam-bizonyíték hiányos külön kísérleti ág, nem a főútvonal cancellation és dangling tesztelve
mérési lefedettség nem minden dispatch-változat mért a 39.4 benchmark kiegészítése reprodukálható medián, correctness gate
platform/ABI x86-64 System V az elsődleges labor AArch64 összehasonlítás ABI-teszt a második célon

A review értékelése négy szempont szerint összegezhető. Helyesség: a funkcionális szerződés teljesül-e, és a hibás ágak lefedettek-e. Bonyolultság: a C++ port több implicit szerződést hoz (destruktor, move, konverzió, template), ezért a karbantartási költség nem feltétlenül alacsonyabb; a move-szemantika és a view-k új hibalehetőségek. Ownership: a Reader/unique_ptr/RAII egyértelműbb tulajdonost ad, de a shared_ptr és a borrowed view-k visszahozzák a C pointeres hibákat. Költség: a buildidő, a bináris méret és a futásidejű indirekció mérlegelendő; a „modernebb” nem azonos a „jobb” választással.

39.5.8 Tipikus hibák és javítások

  1. A span visszaadása lokális bufferből. A std::span nem tartja életben a tárolót; a visszaadott nézet dangling. Javítás: az owner élettartamának dokumentálása, vagy érték visszaadása.
  2. A move után a régi fd használata. A move constructor után a forrás fd_ = -1; ha ezt nem tesszük meg, dupla close lesz. Javítás: move-and-swap vagy explicit -1 beállítás, = delete a másolásra.
  3. A close hiba elvesztése. A destruktor nem jelezhet; ha a hiba számít, explicit close() kell. Javítás: külön close() + dokumentált fallback.
  4. errno és error_code keverése. Az errno-t a hívás után azonnal menteni kell; a read és a close külön hibát adhat. Javítás: minden rendszerhívás után azonnali mentés, külön hibacsatorna.
  5. expected::value() ellenőrzés nélkül. Hibás expected esetén dob; a has_value() vagy a monadikus lánc kötelező. Javítás: value_or, and_then, vagy explicit ág.
  6. std::function a hot pathon. Allokálhat és indirektál; a function_ref (C++26) vagy a C callback olcsóbb lehet. Javítás: mérés, majd a legkisebb elégséges mechanizmus.
  7. Kivétel átengedése extern "C" határon. A C ABI nem ismeri a kivételt; terminate vagy nem definiált viselkedés lehet. Javítás: try/catch a határon, hibakódra fordítva.
  8. A C API data pointer élettartamának elfelejtése. A pointer a következő hívásig érvényes; a kliens nem tárolhatja tovább. Javítás: dokumentált szerződés és teszt.

39.5.9 Feladatok

  1. Auditfeladat. A fenti C és C++ példa alapján készíts erőforrás- és hibaszerződés-táblázatot: minden erőforrás, tulajdonos, acquire, release, hibás ág, dupla felszabadítás kockázata. Hasonlítsd össze a goto cleanup és a RAII megoldást, és jelöld, hol veszíthető el hiba.
  2. Kódolási feladat. Írd át a Reader::next-et std::expected-alapúra C++23-on, feature-testtel, és adj C++20-as fallbackot. Tartsd meg a span élettartam-szerződését, és dokumentáld az EOF és a hiba megkülönböztetését.
  3. Diagnosztikai feladat. Készíts mérési tervet a virtual Stage, a template Stage és a std::function közötti választáshoz. Add meg a hipotézist, a független változót, a mérendő metrikákat, a correctness gate-et és az elfogadási feltételt.
  4. Review-feladat. Ellenőrizd a stabil C API határt: extern "C", opaque handle, allocator pár, hibakód-leképezés, kivétel tilalma. Adj öt ellenőrzési pontot és a hozzájuk tartozó bizonyítékot.

39.5.10 Megoldások

1. megoldás. Az erőforrás-táblázat sorai: fd (tulajdonos: diag_reader/Reader; acquire: open; release: diag_close/~Reader; hibás ág: open hiba → nincs fd; read hiba → close a fix ponton); buffer (tulajdonos: a reader; nincs külön release); data/span (nem tulajdonos; érvényes a következő hívásig). A C-ben a diag_close a break után minden ágon lefut; goto cleanup esetén egy címke gyűjti a release-eket. A C++-ban a ~Reader a scope exiten fut, de a close hibáját nem adja vissza. Ellenőrzés: minden acquire után van release, a move után a forrás -1, a data pointer nem él túl egy next-et. Indoklás: a RAII csökkenti a kihagyott release kockázatát, de a hibajelzést explicit close()-ra kell bízni.

2. megoldás. Az expected verzió szignatúrája:

std::expected<std::span<const std::byte>, std::error_code> next();

Az EOF-ot üres std::span{} értékkel, a hibát std::unexpect, ec-vel kell visszaadni. A C++20-as fallback a kimeneti paraméteres error_code marad. A feature-test:

#if defined(__cpp_lib_expected) && __cpp_lib_expected >= 202202L
// expected ág
#else
// C++20 fallback
#endif

Ellenőrzés: minden ágon has_value() vagy error() vizsgálat; a span élettartama a következő hívásig; a C++20 és C++23 változat ugyanazt a kimenetet adja a tesztbemenetekre. Indoklás: az expected típusosabb, de a .value() hibás esetben dob, ezért a hívónak ellenőriznie kell; a fallback megőrzi a C++20-as alapot.

3. megoldás. Hipotézis: a template a hot pathon kevesebb indirekciót ad, de nagyobb binárist és hosszabb buildidőt; a std::function allokálhat és másol; a virtual egyszerű, de indirektál. Független változó: a dispatch mechanizmus. Metrikák: medián futásidő, allokációk száma, bináris méret, buildidő. Correctness gate: ugyanaz a kimenet minden változaton, a teszt a benchmark előtt fut. Elfogadási feltétel: a template csak akkor választandó, ha a nyereség mérhető és a bináris növekedés elfogadható; a std::function csak ott, ahol a típus törlése valóban kell. A fejezet nem állítja, hogy a mérés megtörtént; a terv a CMake, tesztek és mérési csomag (39.4) alfejezet csomagjában futtatandó.

4. megoldás. Ellenőrzési pontok: (a) a header extern "C" blokkban van, C fordítóval is befordul; (b) az opaque struct definíciója nem látszik a kliensben; (c) a create/destroy ugyanazt az allocatort használja; (d) a next hibakódja és a data élettartama dokumentált, és a kliens teszteli; (e) a C++ implementáció try/catch-csel fordítja a kivételt hibakódra, és nem enged kivételt a C ABI-n. Bizonyíték: C kliensprogram, nm -D/readelf -Ws, sanitizer, negatív teszt. Indoklás: a stabil határ nem a C++ típusokon, hanem a C ABI-n keresztül ellenőrizhető.

39.5.11 Összefoglaló

A C++ port review-ja a C capstone viselkedési szerződéséhez méri a változatot, nem a nyelvi modernséghez. A C explicit cleanupja egy kilépési pontot ad, a C++ determinisztikus destruktor nyelvi garanciát, de a destruktor nem jelezhet hibát; a close hibáját explicit close()-ra kell bízni. A pointer+hossz és a std::span ugyanazt a borrowed lifetime-szerződést hordozza; a span nem tulajdonol és nem ellenőriz boundsot. A hibakezelésben a gyakori I/O-hiba error_code/expected, a ritka belső hiba kivétel, a C ABI határán hibakód. A dispatch, a template és a type erasure közötti választás költség- és ABI-kérdés, amelyet mérni vagy legalább indokolni kell. A stabil C API, a tesztbizonyíték és a nyitott kérdések listája teszi a review-t őszintévé; a továbbtanulási terv ezekből a hiányokból épül fel.