8Selitä poikkeusturvallisuuden tasot ja kuinka `noexcept` vaikuttaa siirto-operaatioihin, säiliöihin ja koodin generointiin.
Poikkeusturvallisuuden tasot kuvaavat sitä, mikä pysyy totena, jos operaatio heittää poikkeuksen. Perustakuu (basic guarantee) tarkoittaa, että invariantit säilyvät ja resursseja ei vuoda, vaikka tilamuutoksia on saattanut tapahtua. Vahva takuu (strong guarantee) tarkoittaa vahvista-tai-peruuta -semantiikkaa (commit-or-rollback): operaation epäonnistuessa havaittavissa oleva tila pysyy täysin muuttumattomana. Ei-heittävä takuu (nothrow guarantee) tarkoittaa, että operaatio ei heitä poikkeuksia. Yleisiä tekniikoita poikkeusturvallisuuden saavuttamiseksi ovat RAII (Resource Acquisition Is Initialization), työn suorittaminen väliaikaisilla olioilla, kopioi-ja-vaihda (copy-and-swap) -idiomi ja tilamuutosten järjestäminen niin, että vahvistaminen (commit) tapahtuu vasta, kun poikkeuksia mahdollisesti heittävä työ on onnistunut. `noexcept` on sopimus: jos `noexcept`-funktio heittää poikkeuksen, kutsutaan välittömästi `std::terminate`-funktiota. Se vaikuttaa myös geneeriseen koodiin ja säiliöihin: esimerkiksi muistin uudelleenallokoinnin aikana `std::vector` voi siirtää alkioita, kun siirtokonstruktori on `noexcept`; muussa tapauksessa se saattaa kopioida ne, usein `std::move_if_noexcept`-funktion avulla, säilyttääkseen poikkeusturvallisuuden takuut. `noexcept` voi myös auttaa koodin generoinnissa ja optimoinnissa vähentämällä tarvittavia poikkeusten etenemispolkuja, mutta vain silloin, kun koodin antama lupaus pitää paikkansa.
std::vector<T> v;
// During reallocation, an implementation may effectively do:
T* dest = allocate(new_cap);
for (T& x : old_storage) {
construct(dest++, std::move_if_noexcept(x));
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa
9Selitä `std::expected` tai vastaavat tulostyypit vaihtoehtoina poikkeuksille virhealttiissa operaatioissa.
`std::expected<T, E>` edustaa joko onnistunutta arvoa `T` tai eksplisiittistä virhettä `E`, mikä tekee epäonnistumisesta osan funktion paluuarvotyyppiä sen sijaan, että luotettaisiin poikkeusten välittymiseen. Se on hyödyllinen virhealttiissa operaatioissa, joissa virheet ovat odotettavissa ja kutsujien tulisi käsitellä ne paikallisesti. Tällaisia ovat esimerkiksi jäsennys, validointi, verkkokutsut, tietokantakyselyt ja uudelleenyritettävät taustajärjestelmän operaatiot. Virhetyypin tulisi kantaa rakenteellista tietoa, kuten virhekoodi, kategoria, uudelleenyritettävyys, viesti tai HTTP/RPC-kartoitus. `expected`- ja muut tulostyypit mahdollistavat virheiden tarkistamisen ja edelleen välittämisen komposition avulla, ja C++23-tyylisissä API-määrittelyissä (Application Programming Interface) voidaan käyttää monadisia operaatioita, kuten `and_then`, `transform` ja `or_else`, vaiheiden ketjuttamiseen ilman syvälle sisäkkäin meneviä ehtolauseita. Verrattuna poikkeuksiin ne tekevät ohjausvuosta ja API-sopimuksista eksplisiittisiä ja toimivat hyvin poikkeuksettomissa ympäristöissä tai ABI-rajojen (Application Binary Interface) yli; poikkeukset voivat silti olla sopivia harvinaisiin, ei-paikallisiin tai todella poikkeuksellisiin virhetilanteisiin projektin käytännöistä riippuen.
enum class Errc { BadRequest, NotFound, Timeout, DbUnavailable };
struct Error {
Errc code;
bool retryable;
std::string message;
};
std::expected<User, Error> load_user(UserId id);
HttpResponse handle(UserId id) {
auto user = load_user(id);
if (!user) {
switch (user.error().code) {
case Errc::BadRequest: return {400, user.error().message};
case Errc::NotFound: return {404, "not found"};
case Errc::Timeout: return {503, "try again"};
case Errc::DbUnavailable: return {503, "service unavailable"};
}
}
return {200, serialize(*user)};
}
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa