Std :: exception'dan miras almalı mıyım?


98

En az bir güvenilir kaynak gördüm (aldığım bir C ++ sınıfı), C ++ 'daki uygulamaya özel istisna sınıflarının std::exception. Bu yaklaşımın faydaları konusunda net değilim.

C # 'da miras ApplicationExceptionalmanın nedenleri açıktır: bir avuç kullanışlı yöntem, özellik ve kurucu elde edersiniz ve ihtiyacınız olanı eklemeniz veya geçersiz kılmanız yeterlidir. İle std::exceptionkendisine Alacağınız tüm olduğu görülmektedir what()sadece kendinizi de yaratabilir geçersiz kılmak için yöntem.

Öyleyse, eğer varsa, std::exceptionuygulamaya özel istisna sınıfım için temel sınıf olarak kullanmanın faydaları nelerdir? Miras almamak için iyi sebepler var std::exceptionmı?


Şuna bir göz atmak isteyebilirsiniz: stackoverflow.com/questions/1605778/1605852#1605852
sbi

2
Belirli bir soruyla ilgisi olmayan bir yan not olarak, aldığınız C ++ sınıflarının kendi başlarına iyi uygulamalar konusunda mutlaka güvenilir kaynaklar olması gerekmez.
Christian Rau

Yanıtlar:


69

Ana parası ne tam türünü bilmek zorunda değildir sizin sınıfları kullanarak bu kodu throwona, ama yapamıyor sadece . Düzenleme: Martin ve diğerlerinin belirttiği gibi, aslında başlıkta bildirilen alt sınıflardan birinden türetmek istiyorsunuz .catchstd::exception

std::exception<stdexcept>


21
Std :: exception'a mesaj iletmenin bir yolu yoktur. std :: runtime_error bir dizeyi kabul eder ve std :: exception'dan türetilir.
Martin York

14
İstisna türü kurucusuna bir mesaj iletmemelisiniz (mesajların yerelleştirilmesi gerektiğini düşünün) Bunun yerine, hatayı anlamsal olarak kategorize eden bir istisna türü tanımlayın, kullanıcı dostu bir mesajı biçimlendirmek için ihtiyacınız olanı istisna nesnesinde saklayın. , daha sonra yakalama yerinde yapın.
Emil

std::exceptionolduğu tanımlanan olarak <exception>başlık ( kanıt ). -1
Alexander Shukaev

5
Peki ne demek istiyorsun? Tanım beyanı ima ediyor, burada neyin yanlış olduğunu görüyorsunuz?
Nikolai Fetissov

40

İle ilgili sorun std::exception bir mesajı kabul eden (standart uyumlu sürümlerde) hiçbir kurucu olmamasıdır.

Sonuç olarak türetmeyi tercih ederim std::runtime_error. Bu türetilir, std::exceptionancak yapıcıları , çağrıldığında std::string(a olarak char const*) döndürülecek olan yapıcıya bir C-String veya a iletmenize izin verir what().


11
Atma noktasında kullanıcı dostu bir mesajı biçimlendirmek iyi bir fikir değildir çünkü bu, daha düşük seviyeli kodu yerelleştirme işlevselliği ile birleştirir. Bunun yerine, tüm ilgili bilgileri istisna nesnesinde depolayın ve yakalama sitesinin istisna türüne ve taşıdığı verilere göre kullanıcı dostu bir mesaj biçimlendirmesine izin verin.
Emil

16
@ Emil: Bir istisnaysanız, kullanıcı tarafından görüntülenebilir mesajlar taşıyın, ancak bunlar genellikle yalnızca kayıt amaçlıdır. Fırlatma sitesinde bir kullanıcı mesajı oluşturmak için bağlama sahip değilsiniz (çünkü bu yine de bir kitaplık olabilir). Yakalama sitesi daha fazla içeriğe sahip olacak ve uygun mesajları oluşturabilir.
Martin York

3
İstisnaların yalnızca kayıt amaçlı olduğu fikrini nereden edindiğinizi bilmiyorum. :)
Emil

1
@Emil: Bu tartışmayı çoktan geçtik. İstisnaya koyduğunuz şeyin bu olmadığını söyledim. Konuyu anlıyorsunuz ama argümanınız kusurlu. Kullanıcı için oluşturulan hata mesajları, geliştiricinin günlük mesajlarından tamamen farklıdır.
Martin York

1
@Emil. Bu tartışma bir yaşında. Bu noktada kendi sorunuzu oluşturun. Benim ilgilendiğim kadarıyla argümanlarınız tamamen yanlış (ve oylara göre insanlar benimle aynı fikirde olma eğilimindedir). Bkz Kitaplığıma için uygulamaya istisnalar 'iyi sayı' nedir? ve Genel istisnaları yakalamak gerçekten kötü bir şey mi? Önceki konuşmanızdan bu yana herhangi bir yeni bilgi vermediniz.
Martin York

17

Ondan miras std::exceptionalmanın nedeni, istisnalar için "standart" temel sınıftır, bu nedenle bir ekipteki diğer kişilerin bunu beklemesi ve temel alması doğaldır.std::exception .

Kolaylık arıyorsanız std::runtime_error, std::stringyapıcı sağlayan bundan miras alabilirsiniz .


2
Std :: exception dışında herhangi bir standart istisna türünden türetmek muhtemelen iyi bir fikir değildir. Sorun, std :: istisnadan sanal olmayan bir şekilde türetilmeleridir; bu, birden fazla kalıtım içeren ince hatalara yol açabilir, burada bir catch (std :: exception &), std :: exception olmanın dönüşümü nedeniyle sessizce bir istisnayı yakalayamaz. belirsiz.
Emil

2
Konuyla ilgili boost ile ilgili makaleyi okudum. Saf mantıksal anlamda makul görünüyor. Ancak MH'yi vahşi doğada istisnalarda hiç görmedim. MH'yi istisnai durumlarda kullanmayı da düşünmezdim, bu yüzden var olmayan bir sorunu çözmek için bir balyoz gibi görünüyor. Bu gerçek bir sorun olsaydı, eminim ki, bu kadar bariz bir kusuru düzeltmek için standartlar komitesinden bu konuda bir önlem alırdık. Bu yüzden benim düşünceme göre std :: runtime_error (ve ailesi) atmak veya türetmek için hala mükemmel bir şekilde kabul edilebilir istisnalar.
Martin York

5
@Emil: diğer standart istisnalar DOĞAR yanında std::exceptionbir olan mükemmel bir fikir. Yapmamanız gereken şey, birden fazla kişiden miras almaktır .
Ördek mölemeye

@MooingDuck: Birden fazla standart istisna türünden türetiyorsanız, std :: istisnanın birden çok kez türetilmesi (sanal olmayan) ile sonuçlanacaksınız. Boost.org/doc/libs/release/libs/exception/doc/… sayfasına bakın .
Emil

1
@Emil: Bu söylediklerimden kaynaklanıyor.
Ördek mölemeye

13

Bir keresinde, önceki yazarların girişler, HRESULTS, std :: string, char *, rastgele sınıflar ... her yerde farklı şeyler attığı büyük bir kod tabanının temizliğine katıldım; sadece bir türü adlandırın ve muhtemelen bir yere atıldı. Ve hiçbir ortak temel sınıf yok. İnanın bana, atılan tüm türlerin yakalayabileceğimiz ortak bir temele sahip olduğu ve hiçbir şeyin geçmeyeceğini bildiğimiz noktaya geldiğimizde işler çok daha düzenliydi. Bu yüzden lütfen kendinize (ve gelecekte kodunuzu korumak zorunda kalacak olanlara) bir iyilik yapın ve bunu en baştan bu şekilde yapın.


12

Boost :: exception'dan miras almalısınız . Ek verileri taşımak için çok daha fazla özellik ve iyi anlaşılmış yollar sunar ... tabii ki Boost kullanmıyorsanız , bu öneriyi dikkate almayın.


5
Ancak boost :: exception'ın std :: exception'dan türetilmediğine dikkat edin. Boost :: exception'dan türetirken bile, std :: exception'dan da türetmelisiniz (ayrı bir not olarak, istisna türlerinden türetildiğinde sanal kalıtımı kullanmalısınız.)
Emil

10

Evet, türetmelisiniz std::exception.

Diğerleri, std::exceptionona bir metin mesajı iletememe sorunu olan yanıtını vermişlerdir , ancak, genellikle, bir kullanıcı mesajını atış noktasında biçimlendirmeye çalışmak iyi bir fikir değildir. Bunun yerine, tüm ilgili bilgileri yakalama sitesine taşımak için istisna nesnesini kullanın, bu daha sonra kullanıcı dostu bir mesajı biçimlendirebilir.


6
+1, iyi nokta. Hem endişelerin ayrılması hem de i18n açısından, sunum katmanının kullanıcı mesajını oluşturmasına izin vermek kesinlikle daha iyidir.
John M Gant

@JohnMGant ve Emil: İlginç, bunun nasıl yapılabileceğine dair somut bir örnek verebilir misiniz? Birinin std::exceptionistisna bilgisinden türetilebileceğini ve taşıyabileceğini anlıyorum . Ancak hata mesajını oluşturmaktan / biçimlendirmekten kim sorumlu olacak? ::what()İşlev veya başka bir şey?
alfC

1
İletiyi yakalama sitesinde, büyük olasılıkla uygulama (kitaplık yerine) düzeyinde biçimlendirirsiniz. Yazarak yakalarsınız, daha sonra bilgi için araştırırsınız, ardından uygun kullanıcı mesajını yerelleştirir / biçimlendirirsiniz.
Emil

9

Miras almak isteyebilmenizin nedeni std::exception, o sınıfa göre yakalanan bir istisna atmanıza izin vermesidir, yani:

class myException : public std::exception { ... };
try {
    ...
    throw myException();
}
catch (std::exception &theException) {
    ...
}

6

Kalıtımla ilgili bilmeniz gereken bir sorun, nesne dilimlemedir. Bir throw e;fırlatma ifadesi yazdığınızda , türü, üst düzey cv niteleyicilerinin statik türünden kaldırılarak belirlenen istisna nesnesi olarak adlandırılan geçici bir nesneyi başlatır throw. Beklediğiniz bu olmayabilir. Burada bulabileceğiniz problem örneği .

Kalıtıma karşı bir argüman değil, sadece 'bilinmesi gereken' bilgidir.


3
Sanırım, alıp götürmek "e fırlat". kötüdür ve "fırlatır"; tamam.
James Schek

2
Evet, throw;tamam, ancak böyle bir şey yazmanız gerektiği açık değil.
Kirill V.Lyadvinsky

1
Bu, özellikle yeniden atmanın "throw e;" kullanılarak yapıldığı Java geliştiricileri için acı vericidir.
James Schek

Yalnızca soyut temel türlerden türetmeyi düşünün. Soyut bir temel türü değere göre yakalamak size bir hata verecektir (dilimlemeden kaçınarak); soyut bir temel türü referansla yakalamak ve sonra bir kopyasını atmaya çalışmak size bir hata verecektir (yine dilimlemeden kaçınır.)
Emil

Ayrıca üye işlevi ekleyerek bu sorunu çözebilir raise()gibi: virtual void raise() { throw *this; }. Her türetilmiş istisnada onu geçersiz kılmayı unutmayın.
Justin Time - Monica'yı Yeniden

5

Fark: std :: runtime_error vs std :: exception ()

Eğer İster gerektiğini ondan devralan veya size kalmıştır. Standart std::exceptionve standart soyundan olası bir hata hiyerarşisi yapı (bölme içine teklif logic_errorsubhierarchy ve runtime_errorsubhierarchy) ve olası bir durum nesne arayüzü. Beğendiyseniz - kullanın. Herhangi bir nedenle farklı bir şeye ihtiyacınız varsa - kendi istisna çerçevenizi tanımlayın.


3

Dil zaten std :: istisna attığından, düzgün bir hata raporlaması sağlamak için yine de yakalamalısınız. Kendinize ait tüm beklenmedik istisnalar için de aynı yakalamayı kullanabilirsiniz. Ayrıca, istisnalar atan hemen hemen her kütüphane onları std :: exception'dan türetecektir.

Başka bir deyişle,

catch (...) {cout << "Unknown exception"; }

veya

catch (const std::exception &e) { cout << "unexpected exception " << e.what();}

Ve ikinci seçenek kesinlikle daha iyi.


3

Tüm olası istisnalarınız kaynaklanıyorsa std::exception, yakalama bloğunuz basitçecatch(std::exception & e) ve her şeyi yakalayacağından emin .

İstisnayı yakaladıktan sonra, whatdaha fazla bilgi almak için bu yöntemi kullanabilirsiniz . C ++, ördek yazmayı desteklemez, bu nedenle bir whatyöntemi olan başka bir sınıf, onu kullanmak için farklı bir yakalama ve farklı bir kod gerektirir.


7
std :: exception alt sınıfına girmeyin ve sonra (std :: exception e) yakalama. Std :: exception & (veya bir gösterici) için bir referans yakalamalısınız, aksi takdirde alt sınıf verilerini kesiyorsunuz.
jmucchiello

1
Şimdi bu aptalca bir hataydı - kesinlikle daha iyisini biliyorum. stackoverflow.com/questions/1095225/…
Mark Ransom

3

Herhangi bir standart istisna türünden türetilip türetilmeyeceği ilk sorudur. Bunu yapmak, tüm standart kitaplık istisnaları ve sizin için tek bir istisna işleyicisi sağlar, ancak aynı zamanda bu tür tümünü yakalama işleyicilerini de teşvik eder. Sorun şu ki, kişinin yalnızca nasıl başa çıkılacağını bildiği istisnaları yakalaması gerekir. Örneğin main () 'de, eğer what () dizesi çıkmadan önce son çare olarak günlüğe kaydedilecekse, tüm std :: istisnalarını yakalamak muhtemelen iyi bir şeydir. Bununla birlikte, başka yerlerde iyi bir fikir olma ihtimali düşüktür.

Standart bir istisna türünden türetilip türetilmeyeceğine karar verdikten sonra, asıl soru hangisinin temel olması gerektiğidir. Uygulamanız i18n'ye ihtiyaç duymuyorsa, arama sitesinde bir mesajı biçimlendirmenin, bilgileri kaydetme ve arama sitesinde mesaj oluşturma kadar iyi olduğunu düşünebilirsiniz. Sorun, biçimlendirilmiş mesajın gerekli olmayabilmesidir. Tembel bir mesaj oluşturma şeması kullanmak daha iyidir - belki önceden ayrılmış bellekle. Ardından, mesaj gerekirse, erişim sırasında oluşturulur (ve muhtemelen istisna nesnesinde önbelleğe alınır). Bu nedenle, eğer mesaj atıldığında üretilirse, temel sınıf olarak std :: runtime_error gibi bir std :: istisna türevi gerekir. Mesaj tembel olarak oluşturulmuşsa, std :: exception uygun tabandır.


0

Alt sınıf istisnalarının bir başka nedeni, büyük kapsüllenmiş sistemler üzerinde çalışırken daha iyi bir tasarım yönüdür. Doğrulama mesajları, kullanıcı sorguları, önemli denetleyici hataları vb. Şeyler için yeniden kullanabilirsiniz. Mesajlar gibi tüm doğrulamalarınızı yeniden yazmak veya yeniden aramak yerine, ana kaynak dosyasında basitçe "yakalayabilir", ancak hatayı tüm sınıf kümenizin herhangi bir yerine atabilirsiniz.

Örneğin, ölümcül bir istisna programı sonlandırır, bir doğrulama hatası yalnızca yığını temizler ve bir kullanıcı sorgusu son kullanıcıya bir soru sorar.

Bunu bu şekilde yapmak, aynı sınıfları farklı arayüzlerde yeniden kullanabileceğiniz anlamına da gelir. Örneğin, bir Windows uygulaması mesaj kutusunu kullanabilir, bir web hizmeti html'yi gösterecek ve raporlama sistemi bunu günlüğe kaydedecektir.


0

Bu soru oldukça eski olmasına ve zaten bolca yanıtlanmış olmasına rağmen, istisnalar hakkındaki tartışmalarda bunu sürekli olarak kaçırdığım için C ++ 11'de uygun istisna işlemenin nasıl yapılacağına dair bir not eklemek istiyorum:

Kullanım std::nested_exceptionvestd::throw_with_nested

StackOverflow'da burada ve burada açıklanmaktadır, istisnalarınız hakkında nasıl geriye dönük izleme yapabilirsiniz? bir hata ayıklayıcıya veya zahmetli günlüğe gerek duymadan kodunuzdaki istisnalarınız hakkında, yalnızca iç içe geçmiş istisnaları yeniden atacak uygun bir istisna işleyicisi yazarak anlatılmaktadır.

Bunu herhangi bir türetilmiş istisna sınıfı ile yapabileceğiniz için, böyle bir geri izlemeye birçok bilgi ekleyebilirsiniz! Ayrıca bir geri izleme şunun gibi görüneceği GitHub'daki MWE'me de bakabilirsiniz:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

Alt sınıfa bile ihtiyacın yok std::runtime_errorBir istisna oluştuğunda bol miktarda bilgi almak .

Alt sınıflamada gördüğüm tek fayda (sadece kullanmak yerine std::runtime_error), istisna işleyicinizin özel istisnanızı yakalayıp özel bir şey yapabilmesidir. Örneğin:

try
{
  // something that may throw
}
catch( const MyException & ex )
{
  // do something specialized with the
  // additional info inside MyException
}
catch( const std::exception & ex )
{
  std::cerr << ex.what() << std::endl;
}
catch( ... )
{
  std::cerr << "unknown exception!" << std::endl;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.