Sobes.tech
Middle+

Vai no destruktora var izlaist izņēmumu un to noķert?

sobes.tech AI

Atbilde no AI

Jā, bet tas ir ļoti ieteicams maz ticams. Izņēmuma izmešana no destruktora var novest pie nenoteiktas uzvedības, īpaši, ja destruktors tiek izsaukts steka izplešanās laikā dēļ cita izņēmuma.

#include <iostream>
#include <stdexcept>

struct Resource {
    ~Resource() {
        // Ļoti neiesakāms izsūtīt izņēmumu no destruktora
        std::cerr << "Resource destruktors tiek izsaukts." << std::endl;
        // throw std::runtime_error("Resource izņēmums no destruktora")
    }
};

void func_with_exception() {
    Resource r;
    throw std::runtime_error("izņēmums no func_with_exception")
}

int main() {
    try {
        func_with_exception();
    } catch (const std::exception& e) {
        // Ja izņēmums tiek izsūtīts no destruktora steka izplešanās laikā
        // var notikt terminate() vai cita nenoteikta uzvedība.
        std::cerr << "Noķerts izņēmums: " << e.what() << std::endl;
    }
    return 0;
}

Galvenie iemesli, kāpēc nevajadzētu izsūtīt izņēmumus no destruktoriem:

  • Divkārši izņēmumi: Ja destruktors tiek izsaukts, apstrādājot citu izņēmumu (piemēram, steka izplešanās laikā), jauns izņēmums novedīs pie abu aktīvo izņēmumu pastāvēšanas, kas ir nenoteikta uzvedība un bieži noved pie std::terminate().
  • Nepilnīga resursu atbrīvošana: Ja destruktora laikā rodas kļūda un tiek izsūtīts izņēmums, turpmākās operācijas ar resursiem tajā pašā destruktorā var netikt veiktas, kas noved pie resursu noplūdes vai citiem jautājumiem.
  • Grūti pārvaldīt kļūdas: Izņēmumu apstrāde no destruktoriem sarežģī programmas loģiku un apgrūtina izpildes plūsmas izpratni.

C++11 un jaunākās versijās destruktori tiek uzskatīti par noexcept, kas nozīmē, ka tie nedrīkst izsūtīt izņēmumus. Ja noexcept destruktors izsūtīs izņēmumu, notiks nekavējoties std::terminate(). Lai ļautu destruktoram izsūtīt izņēmumu, to jādeklarē skaidri kā noexcept(false).

#include <iostream>
#include <stdexcept>

struct ResourceMayThrow {
    ~ResourceMayThrow() noexcept(false) {
        std::cerr << "ResourceMayThrow destruktors tiek izsaukts." << std::endl;
        // Šajā gadījumā izņēmumu var izsūtīt, bet tas joprojām nav ieteicams
        // throw std::runtime_error("ResourceMayThrow izņēmums no destruktora")
    }
};

int main() {
    try {
        ResourceMayThrow r;
        // Ja šeit notiek izņēmums, un destruktors arī izsūtīs izņēmumu,
        // tas novedīs pie `std::terminate()`.
    } catch (const std::exception& e) {
        std::cerr << "Noķerta izņēmums main: " << e.what() << std::endl;
    }
    return 0;
}

Tā vietā, lai izsūtītu izņēmumus no destruktoriem, ieteicams izmantot šādus pieejas veidus kļūdu apstrādei resursu atbrīvošanas laikā:

  • Kļūdas kodu atgriešana: Ja destruktors var beigties ar kļūdu, struktūra var glabāt kļūdas karogu, ko var pārbaudīt pēc tā izņemšanas.
  • Žurnāla ieraksts: Reģistrēt kļūdas resursu atbrīvošanas laikā žurnālfailā vai standartkļūdu plūsmā.
  • close() vai release() funkcijas īstenošana: Nodrošināt atsevišķu funkciju, lai skaidri aizvērtu resursu, kas var izsūtīt izņēmumu, un izsaukt to pirms objekta iziešanas no dzīves cikla. RAII objektiem jānodrošina resursu atbrīvošana savos destruktoros, pat ja tieši close() izsaukums nav veikts.
Pieeja Ieteicams Piezīmes
Izņēmuma izsūtīšana Var novest pie std::terminate() un nenoteiktas uzvedības.
Kļūdas koda atgriešana Jā (ja nepieciešams) Prasa skaidru kļūdas pārbaudi pēc objekta izmantošanas.
Žurnāla ieraksts Noderīgs atkļūdošanai, bet neaptur programmas izpildi.
close()/release() funkcija Ļauj skaidri apstrādāt kļūdas, atbrīvojot resursus, drošs destruktors.

Kopumā drošs destruktors ir tāds, kas nekad neizsūtīs izņēmumu, kas ir RAII (Resource Acquisition Is Initialization) principa daļa.