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.
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.
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_cppA 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.
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:
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.
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();
#endifA 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.
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.
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.
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
#endifA 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.
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.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.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.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.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.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.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.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.goto cleanup és a RAII megoldást, és
jelöld, hol veszíthető el hiba.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.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.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.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
#endifEllenő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ő.
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.