16-bo‘lim

Xatolarni boshqarish

Istisnolar, std::optional va C++23 std::expected - qaysi biri qachon to'g'ri tanlov.

🕑 13 daqiqa o‘qish 📄 841 so‘z 👁 2 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Xato kodlari - eski usul
  2. Istisnolar
  3. Istisno xavfsizligi darajalari
  4. std::optional
  5. std::expected (C++23)
  6. Uchalasini solishtirish
  7. Xulosa

Xato - bu istisno emas. Xato - bu natija. Savol shundaki, uni qanday qaytarish kerak: qiymat sifatidami yoki chaqiruvni uzib.

Xato kodlari - eski usul #

C++
#include <print>
#include <string>
#include <vector>
#include <optional>
#include <expected>
#include <stdexcept>
#include <charconv>
#include <system_error>
C++
// C usuli - xato kodi qaytariladi, natija parametr orqali
int bolish(int a, int b, int* natija)
{
    if (b == 0) return 1;              // xato kodi
    *natija = a / b;
    return 0;
}

int main()
{
    int javob = 0;

    if (bolish(100, 4, &javob) == 0) {
        std::println("100 / 4 = {}", javob);
    }

    if (bolish(100, 0, &javob) != 0) {
        std::println("100 / 0 - xato kodi qaytdi");
    }

    std::println("");
    // Eng katta muammo: natijani TEKSHIRMASLIK mumkin
    int yomon = 0;
    bolish(100, 0, &yomon);            // qaytish qiymati e'tiborsiz
    std::println("tekshirilmagan natija: {}", yomon);
    std::println("(bu qiymat MA'NOSIZ, lekin hech kim ogohlantirmadi)");

    return 0;
}
Natija
100 / 4 = 25
100 / 0 - xato kodi qaytdi

tekshirilmagan natija: 0
(bu qiymat MA'NOSIZ, lekin hech kim ogohlantirmadi)
Xato kodini e'tiborsiz qoldirish oson

Yuqoridagi kodda uch muammo bor:

MuammoOqibat
Qaytish qiymati e'tiborsiz qolishi mumkinXato jimgina o'tib ketadi
Natija chiqish parametridaKonstruktordan foydalanib bo'lmaydi
Xato ma'nosi hujjatda1 nima degani - noma'lum

C++ da to'rt muqobil bor:

VositaQachon
IstisnoKutilmagan, kamdan-kam xato
std::optional<T>"Yo'q" ham normal javob
std::expected<T, E>Xato sababi ham kerak
[[nodiscard]] + kodJuda past darajali kod

Bu bo'limda uchalasini ham ko'ramiz.

Istisnolar #

C++
class HisobXatosi : public std::runtime_error
{
    long long yetishmaydi_;

public:
    HisobXatosi(const std::string& xabar, long long yetishmaydi)
        : std::runtime_error(xabar), yetishmaydi_(yetishmaydi) {}

    long long yetishmaydi() const { return yetishmaydi_; }
};

class Hisob
{
    long long balans_;

public:
    explicit Hisob(long long b) : balans_(b) {}

    void yech(long long summa)
    {
        if (summa <= 0) throw std::invalid_argument{"summa musbat bo'lishi kerak"};
        if (summa > balans_) {
            throw HisobXatosi{"mablag' yetarli emas", summa - balans_};
        }
        balans_ -= summa;
    }

    long long balans() const { return balans_; }
};

int main()
{
    Hisob h{500'000};

    for (long long summa : {200'000LL, 900'000LL, -5LL, 100'000LL}) {
        try {
            h.yech(summa);
            std::println("{:>8} yechildi, qoldi {}", summa, h.balans());
        }
        catch (const HisobXatosi& x) {
            std::println("{:>8} XATO: {} ({} yetishmaydi)",
                         summa, x.what(), x.yetishmaydi());
        }
        catch (const std::invalid_argument& x) {
            std::println("{:>8} XATO: {}", summa, x.what());
        }
    }

    std::println("");
    std::println("yakuniy balans: {}", h.balans());

    return 0;
}
Natija
  200000 yechildi, qoldi 300000
  900000 XATO: mablag' yetarli emas (600000 yetishmaydi)
      -5 XATO: summa musbat bo'lishi kerak
  100000 yechildi, qoldi 200000

yakuniy balans: 200000
Istisnolarni havola bo'yicha tuting
KOD
catch (const HisobXatosi& x)      // TO'G'RI
catch (HisobXatosi x)             // kesiladi
catch (...)                       // faqat oxirgi chora

Qiymat bo'yicha tutish 14-bo'limdagi kesilishga olib keladi: HisobXatosi ni std::runtime_error sifatida tutsangiz, yetishmaydi_ maydoni yo'qoladi.

Tutish tartibi ham muhim: eng aniqdan eng umumiyga. Kompilyator ularni yozilish tartibida sinaydi:

KOD
catch (const HisobXatosi& x)      // avval aniq tur
catch (const std::runtime_error&) // keyin bazasi
catch (const std::exception&)     // keyin eng umumiy

Teskari tartibda yozsangiz, birinchi catch hammasini ushlab qoladi va -Wall bu haqda ogohlantiradi.

Istisno sinflarini std::runtime_error yoki std::logic_error dan voris qiling - shunda catch (const std::exception&) ularni ham ushlaydi.

Istisno xavfsizligi darajalari #

C++
class Ombor
{
    std::vector<std::string> mahsulotlar_;
    int amallar_{};

public:
    // Kuchsiz kafolat YO'Q - buzilgan holat qolishi mumkin
    void yomonQosh(const std::string& nom, bool xatoQil)
    {
        mahsulotlar_.push_back(nom);
        ++amallar_;
        if (xatoQil) throw std::runtime_error{"yarim yo'lda xato"};
        ++amallar_;             // bu bajarilmay qoladi
    }

    // Kuchli kafolat - yo hammasi, yo hech nima
    void yaxshiQosh(const std::string& nom, bool xatoQil)
    {
        auto nusxa = mahsulotlar_;      // avval nusxada ishlaymiz
        nusxa.push_back(nom);
        if (xatoQil) throw std::runtime_error{"yarim yo'lda xato"};

        mahsulotlar_ = std::move(nusxa);   // faqat oxirida almashtiramiz
        amallar_ += 2;
    }

    std::size_t soni() const { return mahsulotlar_.size(); }
    int amallar() const { return amallar_; }
};

int main()
{
    std::println("KUCHSIZ (buzilgan holat):");
    Ombor a;
    try { a.yomonQosh("non", true); }
    catch (const std::exception& x) { std::println("  tutildi: {}", x.what()); }
    std::println("  mahsulotlar: {}, amallar: {} <- NOMUVOFIQ",
                 a.soni(), a.amallar());

    std::println("");
    std::println("KUCHLI (o'zgarishsiz):");
    Ombor b;
    try { b.yaxshiQosh("non", true); }
    catch (const std::exception& x) { std::println("  tutildi: {}", x.what()); }
    std::println("  mahsulotlar: {}, amallar: {} <- BOSHLANG'ICH HOLAT",
                 b.soni(), b.amallar());

    std::println("");
    std::println("muvaffaqiyatli chaqiruv:");
    b.yaxshiQosh("sut", false);
    std::println("  mahsulotlar: {}, amallar: {}", b.soni(), b.amallar());

    return 0;
}
Natija
KUCHSIZ (buzilgan holat):
  tutildi: yarim yo'lda xato
  mahsulotlar: 1, amallar: 1 <- NOMUVOFIQ

KUCHLI (o'zgarishsiz):
  tutildi: yarim yo'lda xato
  mahsulotlar: 0, amallar: 0 <- BOSHLANG'ICH HOLAT

muvaffaqiyatli chaqiruv:
  mahsulotlar: 1, amallar: 2
Qaysi vositani tanlash optional<T> "Qiymat bo'lmasligi mumkin" • Lug'atda kalit topilmadi • Ro'yxat bo'sh • Ixtiyoriy sozlama Sabab kerak EMAS "yo'q" - bu normal javob expected<T, E> "Qiymat yoki SABAB" • Satrni songa aylantirish • Fayl o'qish • Tarmoq so'rovi Sabab KERAK C++23 - eng yaxshi tanlov throw / catch "Kutilmagan holat" • Konstruktor xatosi • Xotira tugadi • Dastur mantiqi buzilgan KAMDAN-KAM sodir bo'ladi issiq yo'lda ishlatmang Istisno xavfsizligi darajalari 1. noexcept Hech qachon istisno tashlamaydi. Destruktor, swap, ko'chirish. 2. Kuchli Yo hammasi bajariladi, yo holat umuman o'zgarmaydi. 3. Asosiy Holat to'g'ri, lekin qaysi holat - noma'lum. Sizish yo'q. 4. Yo'q Buzilgan obyekt qolishi mumkin. Bunday kod yozmang. Destruktordan istisno TASHLAMANG Stek yechilayotganda ikkinchi istisno tashlansa - std::terminate. Destruktorlar C++11 dan boshlab standart bo'yicha noexcept. Yopish xatosini jurnal fayliga yozing - lekin tashlamang.
Xatoning tabiati vositani tanlaydi

std::optional #

C++
std::optional<int> songaAylantir(const std::string& s)
{
    int natija{};
    auto [oxir, xato] = std::from_chars(s.data(), s.data() + s.size(), natija);

    if (xato != std::errc{} || oxir != s.data() + s.size()) {
        return std::nullopt;
    }
    return natija;
}

std::optional<std::string> topSozlama(
    const std::vector<std::pair<std::string, std::string>>& sozlamalar,
    const std::string& kalit)
{
    for (const auto& [k, q] : sozlamalar) {
        if (k == kalit) return q;
    }
    return std::nullopt;
}

int main()
{
    for (const auto* s : {"42", "-17", "3.14", "salom", "", "1000000"}) {
        auto n = songaAylantir(s);
        std::println("  {:<10} -> {}", std::format("'{}'", s),
                     n ? std::format("{}", *n) : "yaroqsiz");
    }

    std::println("");
    std::vector<std::pair<std::string, std::string>> sozlamalar = {
        {"til", "uz"}, {"mavzu", "tungi"}, {"hajm", "14"}
    };

    for (const auto* k : {"til", "rang", "hajm"}) {
        // value_or - standart qiymat bilan
        std::println("  {:<6} = {}", k,
                     topSozlama(sozlamalar, k).value_or("(standart)"));
    }

    std::println("");
    std::println("optional ning uch usuli:");
    auto q = songaAylantir("99");
    std::println("  bo'sh emasmi:  {}", q.has_value());
    std::println("  *q:            {}", *q);
    std::println("  value_or(0):   {}", q.value_or(0));

    auto bosh = songaAylantir("xxx");
    std::println("  bo'sh.value_or(0): {}", bosh.value_or(0));

    return 0;
}
Natija
  '42'       -> 42
  '-17'      -> -17
  '3.14'     -> yaroqsiz
  'salom'    -> yaroqsiz
  ''         -> yaroqsiz
  '1000000'  -> 1000000

  til    = uz
  rang   = (standart)
  hajm   = 14

optional ning uch usuli:
  bo'sh emasmi:  true
  *q:            99
  value_or(0):   99
  bo'sh.value_or(0): 0
Bo'sh optional ni ochish - aniqlanmagan xatti-harakat
KOD
std::optional<int> q = std::nullopt;
int x = *q;              // UB - tekshirmadingiz
int y = q.value();       // std::bad_optional_access tashlaydi

* va -> tekshirmaydi - value() esa tekshiradi va istisno tashlaydi.

Xavfsiz naqshlar:

KOD
if (auto q = topish(); q) { ishlat(*q); }     // shartda e'lon
int x = q.value_or(0);                        // standart qiymat

std::optional ning qaytish qiymatini e'tiborsiz qoldirish hali ham mumkin. C++20 dan boshlab [[nodiscard]] qo'shing:

KOD
[[nodiscard]] std::optional<int> songaAylantir(...);

Shunda natijani ishlatmasangiz kompilyator ogohlantiradi.

std::expected (C++23) #

C++
enum class Xato { BoshSatr, YaroqsizBelgi, JudaKatta, Manfiy };

std::string xatoMatni(Xato x)
{
    switch (x) {
        case Xato::BoshSatr:      return "satr bo'sh";
        case Xato::YaroqsizBelgi: return "raqam bo'lmagan belgi bor";
        case Xato::JudaKatta:     return "chegaradan oshdi";
        case Xato::Manfiy:        return "manfiy son kutilmagan";
    }
    return "noma'lum";
}

std::expected<int, Xato> yoshOqi(const std::string& s)
{
    if (s.empty()) return std::unexpected{Xato::BoshSatr};

    int natija{};
    auto [oxir, xato] = std::from_chars(s.data(), s.data() + s.size(), natija);

    if (xato == std::errc::result_out_of_range) return std::unexpected{Xato::JudaKatta};
    if (xato != std::errc{} || oxir != s.data() + s.size()) {
        return std::unexpected{Xato::YaroqsizBelgi};
    }
    if (natija < 0) return std::unexpected{Xato::Manfiy};
    if (natija > 150) return std::unexpected{Xato::JudaKatta};

    return natija;
}

int main()
{
    for (const auto* s : {"25", "", "abc", "-5", "200", "99999999999999999999"}) {
        auto n = yoshOqi(s);
        if (n) {
            std::println("  {:<24} -> {} yosh", std::format("'{}'", s), *n);
        } else {
            std::println("  {:<24} -> XATO: {}",
                         std::format("'{}'", s), xatoMatni(n.error()));
        }
    }

    std::println("");
    std::println("zanjir bilan (monadik interfeys):");

    auto natija = yoshOqi("30")
        .transform([](int y) { return y * 12; })
        .transform([](int oy) { return std::format("{} oy", oy); });

    std::println("  '30' -> {}", natija.value_or("xato"));

    auto xatoli = yoshOqi("abc")
        .transform([](int y) { return y * 12; })
        .transform([](int oy) { return std::format("{} oy", oy); });

    std::println("  'abc' -> {}", xatoli.value_or("xato"));
    std::println("  (transform xato holatida O'TKAZIB YUBORILADI)");

    return 0;
}
Natija
  '25'                     -> 25 yosh
  ''                       -> XATO: satr bo'sh
  'abc'                    -> XATO: raqam bo'lmagan belgi bor
  '-5'                     -> XATO: manfiy son kutilmagan
  '200'                    -> XATO: chegaradan oshdi
  '99999999999999999999'   -> XATO: chegaradan oshdi

zanjir bilan (monadik interfeys):
  '30' -> 360 oy
  'abc' -> xato
  (transform xato holatida O'TKAZIB YUBORILADI)
expected - optional ning sababli varianti

std::optional<int> "son bor yoki yo'q" deydi. std::expected<int, Xato> "son bor yoki shu sababdan yo'q" deydi.

Interfeysi deyarli bir xil:

Amaloptionalexpected
Tekshirishif (q)if (q)
Qiymat*q, q.value()*q, q.value()
XatoYo'qq.error()
Standartq.value_or(x)q.value_or(x)
Xato qaytarishreturn std::nullopt;return std::unexpected{e};

Monadik metodlar ikkalasida ham bor:

MetodNima qiladi
transform(f)Qiymatni o'zgartiradi, xatoni o'tkazadi
and_then(f)f ning o'zi expected qaytaradi
or_else(f)Faqat xato holatida chaqiriladi

Bu zanjirlar if ni yo'q qiladi va xato yo'li avtomatik o'tkazib yuboriladi - Rust dagi ? operatoriga o'xshash.

Uchalasini solishtirish #

C++
struct Foydalanuvchi { std::string ism; int yosh{}; };

std::vector<Foydalanuvchi> baza = {
    {"Husanboy", 25}, {"Malika", 22}, {"Nodira", 24}
};

// 1. Istisno
const Foydalanuvchi& topIstisno(const std::string& ism)
{
    for (const auto& f : baza) if (f.ism == ism) return f;
    throw std::out_of_range{"foydalanuvchi topilmadi: " + ism};
}

// 2. optional
std::optional<Foydalanuvchi> topOptional(const std::string& ism)
{
    for (const auto& f : baza) if (f.ism == ism) return f;
    return std::nullopt;
}

// 3. expected
std::expected<Foydalanuvchi, std::string> topExpected(const std::string& ism)
{
    if (ism.empty()) return std::unexpected{"ism bo'sh berildi"};
    for (const auto& f : baza) if (f.ism == ism) return f;
    return std::unexpected{"'" + ism + "' bazada yo'q"};
}

int main()
{
    std::println("1. ISTISNO:");
    for (const auto* n : {"Malika", "Jasur"}) {
        try {
            std::println("   {} -> {} yosh", n, topIstisno(n).yosh);
        } catch (const std::out_of_range& x) {
            std::println("   {} -> tutildi: {}", n, x.what());
        }
    }

    std::println("");
    std::println("2. OPTIONAL:");
    for (const auto* n : {"Nodira", "Jasur"}) {
        auto f = topOptional(n);
        std::println("   {} -> {}", n, f ? std::format("{} yosh", f->yosh)
                                          : "topilmadi (sabab noma'lum)");
    }

    std::println("");
    std::println("3. EXPECTED:");
    for (const auto* n : {"Husanboy", "Jasur", ""}) {
        auto f = topExpected(n);
        std::println("   '{}' -> {}", n,
                     f ? std::format("{} yosh", f->yosh) : f.error());
    }

    return 0;
}
Natija
1. ISTISNO:
   Malika -> 22 yosh
   Jasur -> tutildi: foydalanuvchi topilmadi: Jasur

2. OPTIONAL:
   Nodira -> 24 yosh
   Jasur -> topilmadi (sabab noma'lum)

3. EXPECTED:
   'Husanboy' -> 25 yosh
   'Jasur' -> 'Jasur' bazada yo'q
   '' -> ism bo'sh berildi
Amaliy tanlov qoidasi

So'rang: chaqiruvchi bu holatni kutadimi?

JavobVosita
Ha, va sabab kerak emasoptional
Ha, va sabab kerakexpected
Yo'q, bu dastur xatosiIstisno
Yo'q, bu buzilgan mantiqassert yoki std::terminate

Misollar:

VaziyatTanlov
Lug'atda kalit yo'qoptional
Foydalanuvchi kiritgan matn yaroqsizexpected
Fayl ochilmadiexpected
Konfiguratsiya fayli buzilganIstisno
Konstruktorda invariant buzildiIstisno
Xotira tugadiIstisno (std::bad_alloc)
Massiv indeksi noto'g'riassert (dasturchi xatosi)

Istisnoning kuchi - konstruktorda ishlashi. Konstruktor qiymat qaytara olmaydi, shuning uchun invariant buzilganda yagona yo'l - throw.

Amaliy topshiriq
  1. Xato kodi qaytaradigan C uslubidagi funksiya yozing va natijasini e'tiborsiz qoldiring.
  2. std::runtime_error dan voris qilib o'z istisno turingizni yozing.
  3. Istisnoni qiymat bo'yicha tuting - qo'shimcha maydon saqlanib qoldimi?
  4. catch bloklarini teskari tartibda yozing - kompilyator nima deydi?
  5. Kuchli istisno kafolatini beradigan funksiya yozing.
  6. std::optional qaytaruvchi qidiruv funksiyasi yozing.
  7. Bo'sh optional da value() va * ni solishtiring.
  8. std::expected<T, E> bilan xato sababini qaytaring.
  9. transform zanjirini xato holatda sinang.
  10. Bir xil vazifani uchala usulda yozib, imzolarini solishtiring.

Xulosa #

  • Xato kodi e'tiborsiz qolishi mumkin - eng zaif usul.
  • Istisnolarni const havola bo'yicha tuting, aks holda kesiladi.
  • catch bloklarini aniqdan umumiyga tartibda yozing.
  • Kuchli kafolat: yo hammasi bajariladi, yo holat o'zgarmaydi - nusxada ishlab, oxirida almashtiring.
  • Destruktordan hech qachon istisno tashlamang.
  • std::optional<T> - "yo'q" ham normal javob bo'lganda.
  • Bo'sh optional da * - aniqlanmagan xatti-harakat, value() esa istisno tashlaydi.
  • std::expected<T, E> (C++23) - qiymat yoki sabab.
  • std::unexpected{e} bilan xato qaytariladi, q.error() bilan o'qiladi.
  • transform, and_then, or_else zanjirlari xato yo'lini avtomatik o'tkazadi.
  • Istisnoning yagona o'rin bosilmas joyi - konstruktor.

Keyingi bo'limda standart kutubxona ning qolgan qismini - vaqt, fayl tizimi va tasodifiy sonlarni ko'ramiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.