Menu

C++ İstisnaları: throw, what() ve Hata Yönetimi

İstisnalar, bir fonksiyonun yerel olarak ele alamadığı hataları bildirir. throw kullanmayı, standart istisna türlerinin neler olduğunu, what() mesajını ve gerçekten önemli olan hatalarda istisnaların neden dönüş kodlarından üstün olduğunu öğrenin.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

İstisnalar Neden Var

Önceki sayfada hata durumlarına anlamlı adlar vermek için enum class kullandınız. Bu, bir fonksiyonun beklediği ve çağıranın incelemesi gereken sonuçlar için harikadır. Ancak bazı hatalar farklıdır: çağrı yığınınızın derinliklerindeki bir fonksiyon, bir dosyanın açılmadığını veya bir argümanın hiç mantıklı olmadığını keşfeder ve programın bu konuda ne yapması gerektiği hakkında hiçbir fikri yoktur. Bir hata kodu döndürmek, yalnızca zincirdeki her çağıran onu kontrol etmeyi ve yukarı iletmeyi hatırlarsa işe yarar. Tek bir kontrolü atlarsanız program çöple yoluna devam eder.

İstisnalar bunu çözer. Bir şeyler ters gittiğinde bir nesneyi throw ile fırlatırsınız. Yürütme hemen durur, yığın geri sarılır (fırlatma ile işleyici arasındaki her yerel nesnenin yıkıcısı çalışır) ve kontrol, en yakın eşleşen catch'e atlar. Ele alınmayan bir istisna sessizce göz ardı edilemez: hiçbir şey onu yakalamazsa program std::terminate çağırır ve sonlanır.

Bu sayfa fırlatma tarafına odaklanır: hata nesnelerinin kendisine. Sonraki sayfa try/catch mekanizmasını ayrıntılı olarak inceler.

Fırlatma ve what() Mesajı

Teknik olarak herhangi bir değeri throw ile fırlatabilirsiniz —throw 42; veya throw "oops"; geçerlidir— ama yapmayın. Herkesin uyduğu kural, std::exception'tan türeyen bir nesne fırlatmaktır. Bu temel sınıf tek bir sanal metot bildirir, what(), ki bu da sorunun const char* türünde bir açıklamasını döndürür. Kurala bağlı kalmak, tek bir catch (const std::exception& e)'nin her şeyi ele alabileceği anlamına gelir.

<stdexcept> başlığı, yapıcısı mesajı alan hazır türler sunar:

what()'in, istisnayı oluşturduğunuz dizenin tam olarak aynısını döndürdüğüne dikkat edin. Ayrıca bir runtime_error fırlatmış olmamıza rağmen onu const exception& ile yakaladığımıza da dikkat edin: bu işe yarar çünkü runtime_error bir std::exception'tır (kalıtım sayfasından tanıyacağınız bir ilişki).

Standart İstisna Hiyerarşisi

Kendi istisna türünüzü yazmadan önce, standart kütüphanenin uygun bir tane sunup sunmadığını kontrol edin. Hepsi std::exception'tan türer ve <stdexcept> içinde iki aileye ayrılır:

  • logic_error — prensipte çalıştırmadan önce yakalanabilecek, programın mantığındaki bir hata. Alt türleri arasında invalid_argument, out_of_range, domain_error ve length_error bulunur.
  • runtime_error — yalnızca çalışma zamanında ortaya çıkan ve aslında bir programlama hatası olmayan bir başarısızlık. Alt türleri arasında range_error, overflow_error ve underflow_error bulunur.

Birçok kütüphane fonksiyonu bunları sizin yerinize fırlatır. Örneğin std::vector::at() sınır denetimi yapar ve sonun ötesini okumanıza izin vermek yerine out_of_range fırlatır:

Bu at(), v[9]'un güvenli karşılığıdır. Sade operator[] sınır denetimi yapmaz: burada v[9]'u okumak tanımsız davranıştır, bir istisna değil. at() seçmek, sessiz bir bozulmayı yakalanabilir bir hataya dönüştürme yolunuzdur.

Hatayı tanımlayan türü seçin: bir çağıran anlamsız bir şey geçirdiğinde invalid_argument, indeks/anahtar sorunları için out_of_range, "dış dünya bana karşı başarısız oldu" durumları için runtime_error.

Kendi İstisna Türünüzü Yazmak

Hiçbir standart tür uymadığında —ekstra veri eklemek veya yalnızca kendi hatanızı catch ile yakalayıp başka hiçbir şeyi yakalamamak istediğinizde— std::exception'tan (veya alt türlerinden birinden) türeyen bir sınıf tanımlayın ve what()'i geçersiz kılın. std::runtime_error'tan türemek en kolay yoldur çünkü mesajı zaten saklar ve what()'i sizin yerinize uygular:

NetworkError bir durum kodu taşıdığı için işleyici buna tepki verebilir: bir 5xx'te yeniden dener, bir 4xx'te vazgeçer. Sade bir hata dizesi bunu yapamazdı. Özel tür ayrıca bir catch (const NetworkError&)'nin yalnızca ağ sorunlarını yakalamasına ve geri kalan her şeyi altındaki daha genel işleyiciye bırakmasına olanak tanır.

Eğer bir gün doğrudan std::exception'tan (runtime_error'tan değil) türetirseniz, what()'i kendiniz geçersiz kılmayı ve temel imzayla eşleşmesi için noexcept ile işaretlemeyi unutmayın:

class ParseError : public std::exception {
public:
    const char* what() const noexcept override {
        return "failed to parse input";
    }
};

Değere Göre Fırlat, Referansa Göre Yakala

Bu, C++ istisnalarının en önemli kuralıdır ve yeni başlayanların yanlış yaptığı kuraldır. Nesneleri değere göre fırlatın ve const referansa göre yakalayın.

throw runtime_error("oops");            // değere göre - doğru
catch (const runtime_error& e) { ... }  // const referansa göre - doğru

Bunun yerine değere göre yakalamak —catch (std::exception e)— istisnayı bir temel sınıf nesnesine kopyalar ve türetilmiş kısmı dilimleyerek atar. Dilimlemeden sonra e.what(), sizin geçersiz kıldığınız değil, temel uygulamayı çağırır, böylece özenle hazırladığınız mesaj kaybolur:

try {
    throw NetworkError(503, "service unavailable");
} catch (std::exception e) {       // değere göre - nesne dilimleme!
    std::cout << e.what();         // genel mesaj, status() kayboldu
}

Referans (&) gerçek dinamik türü korur, böylece sanal what() doğru şekilde gönderilir ve türetilmiş üyelere hâlâ erişebilirsiniz. const ekleyin çünkü istisnayı yalnızca okuyorsunuz, değiştirmiyorsunuz. Asla bir işaretçi fırlatmayın (throw new runtime_error(...)): yakalayanın onu delete etmesi gerekir, peki hangi kod yolunda? Bu, tam olarak istisnaların önlemesi gereken sızıntıdır.

Sıradaki: try-catch

Artık iyi biçimlendirilmiş istisnaları oluşturup throw ile fırlatabilir ve her hata için doğru standart türü seçebilirsiniz. Hikâyenin diğer yarısı yakalama tarafıdır. Sonraki sayfa try/catch'i tüm yönleriyle ele alır: birden fazla catch bloğunu en özelden en genele sıralamak, her şeyi yakalayan catch (...), sade bir throw; ile yeniden fırlatmak ve RAII'nin (akıllı işaretçileri hatırlayın) yığın geri sarılırken kaynaklarınızın serbest bırakılmasını nasıl garanti ettiği.

Sıkça Sorulan Sorular

C++'ta istisna nedir?

İstisna, mevcut fonksiyonun tek başına ele alamadığı bir hatayı işaret eden bir nesnedir. Onu throw ile fırlatırsınız, yığın geri sarılır (yol boyunca yerel nesneleri yok ederek) ve daha yukarıda eşleşen bir catch bloğu kontrolü devralır. Bu, bir sorunu tespit eden kodu, o sorunla ne yapılacağına karar veren koddan ayırır.

Hatalarda throw ile return arasındaki fark nedir?

Bir return değerinin çağıran tarafından kontrol edilmesi gerekir ve bunu unutmak kolaydır; program bozuk veriyle yoluna devam eder. Fırlatılan bir istisna göz ardı edilemez: kimse onu yakalamazsa program sonlanır. İstisnalar gerçek hatalar içindir (bir dosya açılmaz, girdi geçersizdir); dönüş değerleri ise beklenen "bulunamadı" durumları dahil sıradan sonuçlar için hâlâ doğru seçimdir.

C++ istisnalarında what() metodu ne yapar?

std::exception'tan türeyen her sınıf, hatayı tanımlayan bir const char* döndüren sanal bir what() metodu sağlar. Bir istisnayı yakaladığınızda e.what() çağrısı size günlüğe yazabileceğiniz veya yazdırabileceğiniz, insan tarafından okunabilir mesajı verir. Standart istisna türleri bunu, yapıcılarına geçirdiğiniz dizeden ayarlar.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA