16-bo‘lim
Xatolarni boshqarish
Istisnolar, std::optional va C++23 std::expected - qaysi biri qachon to'g'ri tanlov.
Ushbu bo‘lim mundarijasi
Xato - bu istisno emas. Xato - bu natija. Savol shundaki, uni qanday qaytarish kerak: qiymat sifatidami yoki chaqiruvni uzib.
Xato kodlari - eski usul #
#include <print>
#include <string>
#include <vector>
#include <optional>
#include <expected>
#include <stdexcept>
#include <charconv>
#include <system_error>
// 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;
}
100 / 4 = 25
100 / 0 - xato kodi qaytdi
tekshirilmagan natija: 0
(bu qiymat MA'NOSIZ, lekin hech kim ogohlantirmadi)
Yuqoridagi kodda uch muammo bor:
| Muammo | Oqibat |
|---|---|
| Qaytish qiymati e'tiborsiz qolishi mumkin | Xato jimgina o'tib ketadi |
| Natija chiqish parametrida | Konstruktordan foydalanib bo'lmaydi |
| Xato ma'nosi hujjatda | 1 nima degani - noma'lum |
C++ da to'rt muqobil bor:
| Vosita | Qachon |
|---|---|
| Istisno | Kutilmagan, kamdan-kam xato |
std::optional<T> | "Yo'q" ham normal javob |
std::expected<T, E> | Xato sababi ham kerak |
[[nodiscard]] + kod | Juda past darajali kod |
Bu bo'limda uchalasini ham ko'ramiz.
Istisnolar #
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;
}
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
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:
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 #
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;
}
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
std::optional #
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;
}
'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
optional ni ochish - aniqlanmagan xatti-harakatstd::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:
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:
[[nodiscard]] std::optional<int> songaAylantir(...);
Shunda natijani ishlatmasangiz kompilyator ogohlantiradi.
std::expected (C++23) #
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;
}
'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 variantistd::optional<int> "son bor yoki yo'q" deydi.
std::expected<int, Xato> "son bor yoki shu sababdan
yo'q" deydi.
Interfeysi deyarli bir xil:
| Amal | optional | expected |
|---|---|---|
| Tekshirish | if (q) | if (q) |
| Qiymat | *q, q.value() | *q, q.value() |
| Xato | Yo'q | q.error() |
| Standart | q.value_or(x) | q.value_or(x) |
| Xato qaytarish | return std::nullopt; | return std::unexpected{e}; |
Monadik metodlar ikkalasida ham bor:
| Metod | Nima 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 #
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;
}
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
So'rang: chaqiruvchi bu holatni kutadimi?
| Javob | Vosita |
|---|---|
| Ha, va sabab kerak emas | optional |
| Ha, va sabab kerak | expected |
| Yo'q, bu dastur xatosi | Istisno |
| Yo'q, bu buzilgan mantiq | assert yoki std::terminate |
Misollar:
| Vaziyat | Tanlov |
|---|---|
| Lug'atda kalit yo'q | optional |
| Foydalanuvchi kiritgan matn yaroqsiz | expected |
| Fayl ochilmadi | expected |
| Konfiguratsiya fayli buzilgan | Istisno |
| Konstruktorda invariant buzildi | Istisno |
| Xotira tugadi | Istisno (std::bad_alloc) |
| Massiv indeksi noto'g'ri | assert (dasturchi xatosi) |
Istisnoning kuchi - konstruktorda ishlashi.
Konstruktor qiymat qaytara olmaydi, shuning uchun
invariant buzilganda yagona yo'l - throw.
- Xato kodi qaytaradigan C uslubidagi funksiya yozing va natijasini e'tiborsiz qoldiring.
std::runtime_errordan voris qilib o'z istisno turingizni yozing.- Istisnoni qiymat bo'yicha tuting - qo'shimcha maydon saqlanib qoldimi?
catchbloklarini teskari tartibda yozing - kompilyator nima deydi?- Kuchli istisno kafolatini beradigan funksiya yozing.
std::optionalqaytaruvchi qidiruv funksiyasi yozing.- Bo'sh
optionaldavalue()va*ni solishtiring. std::expected<T, E>bilan xato sababini qaytaring.transformzanjirini xato holatda sinang.- Bir xil vazifani uchala usulda yozib, imzolarini solishtiring.
Xulosa #
- Xato kodi e'tiborsiz qolishi mumkin - eng zaif usul.
- Istisnolarni
consthavola bo'yicha tuting, aks holda kesiladi. catchbloklarini 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
optionalda*- 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_elsezanjirlari 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.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.