8Spiega i livelli di sicurezza rispetto alle eccezioni e come `noexcept` influisce sulle operazioni di spostamento, sui contenitori e sulla generazione del codice.
I livelli di sicurezza rispetto alle eccezioni descrivono cosa rimane valido se un'operazione lancia un'eccezione. La garanzia di base (basic guarantee) significa che gli invarianti vengono preservati e le risorse non vengono perse, sebbene lo stato possa essere cambiato. La garanzia forte (strong guarantee) implica una semantica di commit-or-rollback: in caso di fallimento, lo stato osservabile rimane invariato. La garanzia di assenza di eccezioni (nothrow guarantee) significa che l'operazione non lancia eccezioni. Le tecniche comuni includono RAII (Resource Acquisition Is Initialization), l'esecuzione di operazioni su oggetti temporanei, il copy-and-swap e l'ordinamento delle mutazioni in modo che il commit avvenga solo dopo il successo delle operazioni che possono lanciare eccezioni. `noexcept` è un contratto: se una funzione `noexcept` lancia un'eccezione, viene chiamato `std::terminate`. Influisce anche sul codice generico e sui contenitori: ad esempio, durante la riallocazione `std::vector` può spostare gli elementi quando il costruttore di spostamento è `noexcept`; altrimenti potrebbe copiarli, spesso tramite `std::move_if_noexcept`, per preservare le garanzie sulle eccezioni. `noexcept` può anche aiutare la generazione o l'ottimizzazione del codice riducendo i percorsi di propagazione delle eccezioni necessari, ma solo quando la promessa è corretta.
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));
}
Prova a rispondere a questa domanda con un coach IA
9Spiega `std::expected` o tipi di risultato (outcome) equivalenti come alternative alle eccezioni per le operazioni che possono fallire.
`std::expected<T, E>` rappresenta un valore di successo `T` o un errore esplicito `E`, rendendo il fallimento parte del tipo di ritorno della funzione invece di affidarsi alla propagazione delle eccezioni. È utile per operazioni soggette a errori in cui questi sono previsti e i chiamanti dovrebbero gestirli localmente, come il parsing, la validazione, le chiamate di rete, le ricerche nello storage e le operazioni di backend ripetibili. Il tipo di errore dovrebbe contenere informazioni strutturate come un codice di errore, una categoria, la possibilità di ritentare l'operazione, un messaggio o una mappatura HTTP/RPC (Hypertext Transfer Protocol / Remote Procedure Call). I tipi expected o outcome si compongono controllando e propagando gli errori, e nelle API in stile C++23 possono utilizzare operazioni monadiche come `and_then`, `transform` e `or_else` per concatenare i passaggi senza condizionali profondamente annidati. Rispetto alle eccezioni, rendono espliciti il flusso di controllo e i contratti delle API, e funzionano bene in contesti privi di eccezioni o attraverso i confini dell'ABI (Application Binary Interface); le eccezioni potrebbero rimanere appropriate per fallimenti rari, non locali o veramente eccezionali a seconda delle policy del progetto.
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)};
}
Prova a rispondere a questa domanda con un coach IA