noexcept în C++

Excepții C++11

Marchează o funcție care promite că nu aruncă excepții; ca operator, verifică la compilare dacă o expresie poate arunca.

Sintaxă
void f() noexcept;            // promite că nu aruncă excepții
void g() noexcept(conditie);  // condiționat
noexcept(expresie)            // operator: true dacă expresia nu poate arunca

Ce face noexcept

noexcept după o funcție este o promisiune: funcția nu lasă nicio excepție să iasă din ea. Dacă totuși iese una, programul nu caută catch, ci se oprește imediat cu std::terminate.

Promisiunea ajută la optimizare. Cel mai important caz: std::vector își mută elementele la mărire doar dacă constructorul de mutare al tipului este noexcept (sau dacă tipul nu se poate copia); altfel le copiază, ceea ce e mai lent. Destructorii sunt noexcept implicit.

Ca operator, noexcept(expresie) dă true sau false la compilare, fără să execute expresia. Se folosește în șabloane: void f() noexcept(noexcept(g())).

Exemple

Exemplul 1: Operatorul noexcept

#include <print>
#include <string>

int dublu(int x) noexcept { return x * 2; }
int converteste(const std::string& s) { return std::stoi(s); }

int main() {
    std::println("dublu poate arunca: {}", !noexcept(dublu(2)));
    std::println("converteste poate arunca: {}", !noexcept(converteste("2")));
    std::println("dublu(21) = {}", dublu(21));
}

Rezultat

dublu poate arunca: false
converteste poate arunca: true
dublu(21) = 42

Exemplul 2: Mutare noexcept și std::vector

La mărirea vectorului, elementele de tip Rapid (mutare noexcept) sunt mutate, iar cele de tip Lent sunt copiate, pentru că mutarea lor ar putea arunca la jumătatea operației.

#include <print>
#include <type_traits>
#include <vector>

struct Rapid {
    Rapid() = default;
    Rapid(const Rapid&) { std::println("Rapid: copiere"); }
    Rapid(Rapid&&) noexcept { std::println("Rapid: mutare"); }
};

struct Lent {
    Lent() = default;
    Lent(const Lent&) { std::println("Lent: copiere"); }
    Lent(Lent&&) { std::println("Lent: mutare"); }
};

int main() {
    std::println("Rapid se mută fără excepții: {}", std::is_nothrow_move_constructible_v<Rapid>);
    std::vector<Rapid> a(1);
    a.reserve(10);  // mărire: elementul existent este mutat
    std::vector<Lent> b(1);
    b.reserve(10);  // mărire: elementul existent este copiat
}

Rezultat

Rapid se mută fără excepții: true
Rapid: mutare
Lent: copiere

Greșeli frecvente

  • Marchezi noexcept o funcție care apelează cod ce poate arunca (de exemplu alocare de memorie, std::stoi). Dacă excepția apare, programul se oprește brusc cu std::terminate, fără nicio șansă de tratare.
  • Uiți noexcept la constructorul și operatorul de mutare scrise de tine. Containerele standard vor copia obiectele în loc să le mute.

Comenzi înrudite

  • try, catch: try marchează codul care poate arunca o excepție, iar blocurile catch prind și tratează excepțiile de un anumit tip.
  • throw: Aruncă o excepție: oprește execuția normală și trimite eroarea la cel mai apropiat catch potrivit.
  • referințe (&, &&): O referință este un alt nume pentru o variabilă existentă; tot ce faci cu referința se face de fapt cu variabila originală.
  • std::move: Mută elementele unui interval la o destinație, fără să le copieze (versiunea din <algorithm>).
  • std::vector: Tabloul dinamic din C++: ține elementele unul după altul în memorie și își mărește singur dimensiunea.