bir yıkıcıdan istisnalar atmak


257

Çoğu insan hiçbir zaman bir yıkıcıdan bir istisna atmaz der - bunu yapmak tanımsız davranışa neden olur. Stroustrup, "vektör yıkıcı her bir eleman için yıkıcıyı açıkça çağırıyor." "bir eleman yıkıcı fırlatılırsa garanti vermez" (Ek E3.2'den) .

Bu makale aksini söylüyor gibi - yıkıcıların aşağı yukarı iyi.

Benim sorum şu - eğer bir yıkıcıdan atmak tanımsız davranışla sonuçlanırsa, bir yıkıcı sırasında meydana gelen hataları nasıl ele alırsınız?

Temizleme işlemi sırasında bir hata oluşursa, yok sayıyor musunuz? Yığılı olarak ele alınabilecek ancak yıkıcıda doğru olmayan bir hata ise, yıkıcıdan bir istisna atmak mantıklı değil mi?

Açıkçası bu tür hatalar nadirdir, ancak mümkündür.


36
"Aynı anda iki istisna" bir stok cevabı ama GERÇEK nedeni değil. Asıl sebep, yalnızca bir işlevin son koşullarının karşılanamaması durumunda bir istisna atılmasıdır. Bir yıkıcının son koşulu, nesnenin artık mevcut olmamasıdır. Bu olamaz. Bu nedenle, arızaya eğilimli kullanım ömrü sonu işlemi, nesne kapsam dışına çıkmadan önce ayrı bir yöntem olarak adlandırılmalıdır (mantıklı işlevler normalde yalnızca bir başarı yoluna sahiptir).
spraff

29
@spraff: Söylediklerinizin "RAII'yi fırlat" anlamına geldiğinin farkında mısınız?
Kos

16
@spraff: "nesne kapsam dışına çıkmadan önce ayrı bir yöntem" olarak adlandırmak zorunda kaldığınızda (yazdığınız gibi) aslında RAII'yi atar! Bu tür nesneleri kullanan kod, yıkıcı çağrılmadan önce böyle bir yöntemin çağrılmasını sağlamak zorunda kalacak .. Son olarak, bu fikir hiç yardımcı olmuyor.
Frunsi

8
@Frunsi hayır, çünkü bu sorun yıkıcının sadece kaynakların serbest bırakılmasının ötesinde bir şeyler yapmaya çalışmasından kaynaklanıyor. "Ben her zaman XYZ yapmak istiyorum" ve bunun böyle bir mantığı yıkıcıya koymak için bir argüman olduğunu düşünmek cazip gelebilir. Hayır, tembel olmayın, xyz()yıkıcıyı RAII dışı mantıktan temiz tutun.
spraff

6
@Frunsi Örneğin, bir şeyi dosyalamak bir işlemi temsil eden bir sınıfın yıkıcısında yapmak uygun değildir . Taahhüt başarısız olursa, işleme dahil olan tüm kod kapsam dışında kaldığında işlemek için çok geç. Bir commit()yöntem çağrılmadığı sürece yıkıcı işlemi atmalıdır .
Nicholas Wilson

Yanıtlar:


198

Bir yıkıcıdan istisna atmak tehlikelidir.
Başka bir istisna zaten yayılıyorsa, uygulama sonlandırılır.

#include <iostream>

class Bad
{
    public:
        // Added the noexcept(false) so the code keeps its original meaning.
        // Post C++11 destructors are by default `noexcept(true)` and
        // this will (by default) call terminate if an exception is
        // escapes the destructor.
        //
        // But this example is designed to show that terminate is called
        // if two exceptions are propagating at the same time.
        ~Bad() noexcept(false)
        {
            throw 1;
        }
};
class Bad2
{
    public:
        ~Bad2()
        {
            throw 1;
        }
};


int main(int argc, char* argv[])
{
    try
    {
        Bad   bad;
    }
    catch(...)
    {
        std::cout << "Print This\n";
    }

    try
    {
        if (argc > 3)
        {
            Bad   bad; // This destructor will throw an exception that escapes (see above)
            throw 2;   // But having two exceptions propagating at the
                       // same time causes terminate to be called.
        }
        else
        {
            Bad2  bad; // The exception in this destructor will
                       // cause terminate to be called.
        }
    }
    catch(...)
    {
        std::cout << "Never print this\n";
    }

}

Bu temelde aşağıdakilere kadar kaynar:

Tehlikeli olan herhangi bir şey (yani bir istisna oluşturabilir), genel yöntemlerle yapılmalıdır (doğrudan olması gerekmez). Daha sonra sınıfınızın kullanıcısı, genel yöntemleri kullanarak ve olası istisnaları yakalayarak bu durumları ele alabilir.

Yıkıcı daha sonra bu yöntemleri çağırarak nesneyi bitirir (kullanıcı bunu açıkça yapmadıysa), ancak herhangi bir istisna atılır ve (sorunu düzeltmeye çalıştıktan sonra) bırakılır.

Böylece aslında sorumluluğu kullanıcıya aktarırsınız. Kullanıcı istisnaları düzeltebilecek durumda ise, uygun işlevleri manuel olarak çağırır ve hataları işler. Nesnenin kullanıcısı endişelenmezse (nesne yok edileceği gibi), yıkıcı işle ilgilenmeye bırakılır.

Bir örnek:

std :: fstream

Close () yöntemi potansiyel olarak bir istisna atabilir. Dosya açılmışsa yıkıcı close () öğesini çağırır, ancak istisnaların yıkıcıdan yayılmadığından emin olur.

Bu nedenle, bir dosya nesnesinin kullanıcısı, dosyayı kapatma ile ilgili sorunlar için özel işlem yapmak istiyorsa, elle close () öğesini çağırır ve istisnaları işler. Öte yandan umursamıyorlarsa, yıkıcı durumu idare etmek için bırakılacaktır.

Scott Myers'ın "Effective C ++" adlı kitabında konuyla ilgili mükemmel bir makale var

Düzenle:

Görünüşe göre "Daha Etkili C ++" da
Madde 11: İstisnaların yıkıcıları terk etmesini önleyin


5
"Uygulamayı potansiyel olarak sonlandırmanın bir sakıncası yoksa, büyük olasılıkla hatayı yutmalısınız." - bu kuraldan ziyade muhtemelen istisna (pardon pun) olmalıdır - yani hızlı başarısız olmak.
Erik Forbes

15
Katılmıyorum. Programın sonlandırılması yığının gevşemesini durdurur. Artık yıkıcı çağrılmayacak. Açılan kaynaklar açık bırakılacaktır. İstisna yutmak tercih edilen seçenek olacağını düşünüyorum.
Martin York

20
OS acan sahibi kapalı kaynakları temizlemek. Bellek, FileHandles vb. Karmaşık kaynaklar hakkında: DB bağlantıları. Açtığınız ISS'ye olan o bağlantı (otomatik olarak yakın bağlantıları gönderecek mi)? Eminim NASA, bağlantıyı temiz bir şekilde kapatmanızı isteyecektir!
Martin York

7
Bir uygulama iptal edilerek "hızlı başarısız" yapacaksa, ilk etapta istisnalar atmamalıdır. Denetimi yığının üstünden geçerek başarısız olursa, programın iptal edilmesine neden olacak şekilde yapmamalıdır. Biri ya da diğeri, ikisini de seçmeyin.
Tom

2
@LokiAstari Bir uzay aracıyla iletişim kurmak için kullandığınız aktarım protokolü kesilen bir bağlantıyı işleyemiyor mu? Tamam ...
doug65536

54

Bir yıkıcıdan atmak çökmeye neden olabilir, çünkü bu yıkıcı "Yığın gevşemenin" bir parçası olarak adlandırılabilir. Yığın çözme, bir istisna atıldığında gerçekleşen bir prosedürdür. Bu prosedürde, "dene" den bu yana ve istisna atılana kadar yığına itilen tüm nesneler sonlandırılacaktır -> yıkıcıları çağrılır. Ve bu prosedür sırasında, başka bir istisna atmasına izin verilmez, çünkü bir seferde iki istisnayı ele almak mümkün değildir, bu nedenle, bu bir iptal çağrısına neden olur (), program çökecek ve kontrol OS'ye geri dönecektir.


1
Lütfen yukarıdaki durumda iptal () yönteminin nasıl çağrıldığını ayrıntılı bir şekilde açıklar mısınız? Yürütme kontrolünün hala C ++ derleyicisi ile olduğu anlamına gelir
Krishna Oza

1
@Krishna_Oza: Oldukça basit: Bir hata atıldığında, bir hata oluşturan kod, çalışma zamanı sisteminin yığın gevşetme sürecinde olduğunu gösteren bir miktar kontrol eder (yani, başkalarını idare eder, throwancak henüz bir catchblok bulamadı ) bu durumda std::terminate( abortyeni) bir istisna oluşturmak (veya yığını gevşemeye devam etmek) yerine (değil ) çağrılır.
Marc van Leeuwen

53

Biz zorundayız ayırt aşağıdaki burada yerine körü körüne genel bir tavsiyeniz özgü durumlar.

Aşağıdakilerin , nesnelerin kapları konusunu ve kapların içindeki nesnelerin birden çok dördü karşısında ne yapılacağını göz ardı ettiğini unutmayın . (Ve bazı nesneler bir kaba koymak için uygun olmadığından kısmen göz ardı edilebilir.)

Sınıfları iki tipte böldüğümüzde tüm problem düşünmek daha kolay hale geliyor. Sınıf dtorunun iki farklı sorumluluğu olabilir:

  • (R) semantik salıverme
  • (C) işlemek semantik (aka floş diske dosyası)

Soruyu bu şekilde görüntülersek, o zaman (R) anlambilimin asla bir dtordan istisnaya neden olmaması gerektiğini savunabiliriz, çünkü a) bu konuda yapabileceğimiz hiçbir şey yok ve b) birçok serbest kaynak işlemi hatta hata kontrolü sağlamak, örneğin .void free(void* p);

Başarıyla ihtiyaçları farklı türden olan dtor içinde taahhüt vermez 's veri veya bir ( "kapsam korunan") veritabanı bağlantısı durulamanızı bir dosya nesne gibi (C) semantik ile Nesneler: Biz olabilir hata hakkında bir şey yapmak (üzerine ve hiçbir şey olmamış gibi devam etmemeliyiz.

RAII yolunu takip edersek ve kendi dillerinde (C) semantiğe sahip olan nesnelere izin verirsek, sanırım bu tür dürtülerin atabileceği garip duruma da izin vermeliyiz. Bu tür nesneleri kaplara terminate()koymamanız gerektiği sonucuna varır ve başka bir istisna etkinken bir komite atarsa programın yine de yapabileceği de izlenir .


Hata işleme (Taahhüt / Geri Alma semantiği) ve istisnalar ile ilgili olarak, bir Andrei Alexandrescu'nun iyi bir konuşması var : C ++ / Deklaratif Kontrol Akışında Hata İşleme ( NDC 2014'te gerçekleştirildi )

Detaylarda, Folly kütüphanesinin takımları UncaughtExceptionCounteriçin nasıl uyguladığını açıklar ScopeGuard.

( Başkalarının da benzer fikirleri olduğunu belirtmeliyim .)

Konuşma bir d'tor'dan atmaya odaklanmasa da, bir d'tor'dan ne zaman atılacağı ile ilgili sorunlardan kurtulmak için bugün kullanılabilecek bir araç gösteriyor .

In geleceği , orada olabilir bunun için bir std özellik bkz N3614 , ve bu konuda tartışma .

Upd '17: Bunun için C ++ 17 std özelliği std::uncaught_exceptionsafaikt. Hızlı bir şekilde cppref makalesini alıntılayacağım:

notlar

int-Returning'in uncaught_exceptionskullanıldığı bir örnek ... ... önce bir koruma nesnesi oluşturur ve yapıcısında yakalanmayan istisnaların sayısını kaydeder. Çıkış, foo () atmadığı sürece koruma nesnesinin yıkıcısı tarafından gerçekleştirilir ( bu durumda, yıkıcıdaki yakalanmamış istisnaların sayısı, kurucunun gözlemlediğinden daha fazladır )


6
Kesinlikle katılıyorum. Ve bir semantik (Ro) geri alma semantiği daha ekleyerek. Kapsam koruyucusunda yaygın olarak kullanılır. Bir ON_SCOPE_EXIT makrosu tanımladığım projemdeki durum gibi. Geri alma semantiği hakkında burada anlamlı bir şey olabilir. Bu yüzden başarısızlığı gerçekten görmezden gelmemeliyiz.
Weipeng L

Yıkıcılarda anlambilimimizin tek nedeni C ++ 'ın desteklememesi finally.
user541686

@Mehrdad: finally olan bir dtor. Ne olursa olsun her zaman denir. Son olarak sözdizimsel yaklaşımı için çeşitli scope_guard uygulamalarına bakın. Günümüzde, dtorun atmasına izin verilip verilmediğini tespit etmek için makineler yerinde (standartta bile, C ++ 14?), Tamamen güvenli hale getirilebilir.
Martin Ba

1
@MartinBa: Sana bu yana şaşırtıcı benim yorumum, noktası cevapsız düşünüyorum kabul (R) ve (C) farklı olduğunu da kavramı ile. Bir dtor'un doğal olarak (R) için bir araç olduğunu ve finallydoğal olarak (C) için bir araç olduğunu söylemeye çalışıyordum . Nedenini görmüyorsanız: finallybloklar halinde birbirinin üzerine istisnalar atmanın neden meşru olduğunu ve bunun neden yıkıcılar için olmadığını düşünün . (Bir anlamda, bu bir veri ve kontrol şeyidir. Yıkıcılar verileri finallyserbest bırakmak içindir, kontrolü serbest bırakmak içindir.
Farklıdırlar

1
@Mehrdad: Burada çok uzun oluyor. İsterseniz, argümanlarınızı burada oluşturabilirsiniz: programmers.stackexchange.com/questions/304067/… . Teşekkürler.
Martin Ba

21

Bir yıkıcıdan fırlatma hakkında kendinize sormanız gereken asıl soru "Arayan bununla ne yapabilir?" Aslında, bir yıkıcıdan fırlayarak yaratılan tehlikeleri telafi edecek istisna dışında yapabileceğiniz faydalı bir şey var mı?

Bir Foonesneyi yok edersem ve Fooyıkıcı bir istisna atarsa , onunla makul olarak ne yapabilirim? Giriş yapabilir veya yok sayabilirim. Bu kadar. "Düzeltemiyorum", çünkü Foonesne çoktan gitti. En iyi durumda, istisnayı kaydedip hiçbir şey olmamış gibi devam ediyorum (veya programı sonlandırıyorum). Bu gerçekten bir yıkıcıdan fırlayarak tanımlanamayan davranışlara neden olmaya değer mi?


11
Sadece fark ettim ... bir dtordan atmak asla Tanımsız Davranış değildir. Elbette, terminate () olarak adlandırılabilir, ancak bu çok iyi belirlenmiş bir davranıştır.
Martin Ba

4
std::ofstream's destructor dosyayı temizler ve sonra kapatır. Yıkama sırasında kesinlikle yararlı bir şeyler yapabileceğiniz bir disk dolu hatası oluşabilir: kullanıcıya diskin boş alanı kalmadığını söyleyen bir hata iletişim kutusu gösterin.
Andy

13

Tehlikelidir, ancak okunabilirlik / kod anlaşılabilirliği açısından da bir anlam ifade etmez.

Sormanız gereken şey bu durumda

int foo()
{
   Object o;
   // As foo exits, o's destructor is called
}

İstisna ne olmalı? Arayan kişi foo olmalı mı? Yoksa foo üstesinden gelmeli mi? Neden foo arayanı foo içindeki bazı nesnelere önem vermeli? Dilin bunu mantıklı olarak tanımlamanın bir yolu olabilir, ancak okunamayacak ve anlaşılması zor olacaktır.

Daha da önemlisi, Object hafızası nereye gidiyor? Nesnenin sahip olduğu bellek nereye gidiyor? Hala tahsis edilmiş mi (görünüşe göre yıkıcı başarısız olduğu için)? Nesnenin yığın alanında olduğunu da düşünün , bu yüzden açıkçası ne olursa olsun gitti.

O zaman bu davayı düşünün

class Object
{ 
   Object2 obj2;
   Object3* obj3;
   virtual ~Object()
   {
       // What should happen when this fails? How would I actually destroy this?
       delete obj3;

       // obj 2 fails to destruct when it goes out of scope, now what!?!?
       // should the exception propogate? 
   } 
};

Obj3'ün silinmesi başarısız olduğunda, aslında başarısız olmadığı garanti edilen bir şekilde nasıl silebilirim? Bu benim hatıram!

Şimdi ilk kod snippet'inde Object3 yığındayken yığında olduğu için nesne otomatik olarak gider. Object3'e işaretçi gittiğinden, bir çeşit SOL'siniz. Bellek sızıntısı var.

Şimdi bir şey yapmanın güvenli bir yolu şudur:

class Socket
{
    virtual ~Socket()
    {
      try 
      {
           Close();
      }
      catch (...) 
      {
          // Why did close fail? make sure it *really* does close here
      }
    } 

};

Ayrıca bu SSS'ye bakın


Bu cevabı yeniden diriltmek gerekirse: ilk örnek, hakkında int foo(), eğer dikkat ederseniz, yıkıcıları yakalamak da dahil olmak üzere tüm fonksiyon foo'yu bir try-catch bloğuna sarmak için bir fonksiyon-try-block kullanabilirsiniz. Yine de tercih edilen yaklaşım değil, ama bu bir şey.
tyree731

13

C ++ için ISO taslağından (ISO / IEC JTC 1 / SC 22 N 4411)

Bu nedenle yıkıcılar genellikle istisnaları yakalamalı ve yıkıcıdan dışarı yayılmalarına izin vermemelidir.

3 Bir try bloğundan atma ifadesine giden yolda oluşturulan otomatik nesneler için yıkıcıları çağırma işlemine “yığın çözme” denir. [Not: Yığın çözme sırasında çağrılan bir yıkıcı bir istisna dışında çıkarsa, std :: terminate çağrılır (15.5.1). Bu nedenle yıkıcılar genellikle istisnaları yakalamalı ve yıkıcıdan dışarı yayılmalarına izin vermemelidir. - son not]


1
Soruya cevap vermedi - OP zaten bunun farkında.
Arafangion

2
@Arafangion Kabul edilen cevap tam olarak aynı noktaya geldiğinden bunun farkındaydı (std :: sonlandırılıyor).
lothar

@Arafangion burada bazı cevaplarda olduğu gibi bazı insanlar kürtaj () denilen söz; Yoksa std :: terminate sırayla abort () işlevini çağırır.
Krishna Oza

7

Yıkıcınız diğer yıkıcılar zincirinin içinde idam ediyor olabilir. Anında arayan tarafından yakalanmayan bir istisna atmak, birden çok nesneyi tutarsız bir durumda bırakabilir, böylece temizleme işleminde hatayı yoksayarak daha fazla soruna neden olabilir.


7

Yıkıcıya atılan "kapsam koruma" deseninin birçok durumda - özellikle birim testler için - yararlı olduğunu düşünen gruptayım. Bununla birlikte, C ++ 11'de, bir yıkıcıyı atmanın, std::terminateyıkıcıların örtülü olarak açıklandığı için bir çağrı ile sonuçlandığını unutmayın noexcept.

Andrzej Krzemieński'nin yıkıcı yıkıcılar konusunda harika bir yazısı var:

C ++ 11'in noexceptyıkıcılar için varsayılanı geçersiz kılma mekanizmasına sahip olduğuna dikkat çekiyor :

C ++ 11'de, bir yıkıcı dolaylı olarak belirtilir noexcept. Hiçbir şartname eklemeseniz ve yıkıcıyı böyle tanımlasanız bile:

  class MyType {
        public: ~MyType() { throw Exception(); }            // ...
  };

Derleyici hala yıkıcıya görünmez özellikler ekleyecektir noexcept. Ve bu, yok edicinizin bir istisna attığı anda, std::terminateçift ​​istisna durumu olmasa bile çağırılacağı anlamına gelir . Yıkıcılarınızın fırlatmasına gerçekten izin verirseniz, bunu açıkça belirtmeniz gerekir; üç seçeneğiniz var:

  • Yıkıcınızı açıkça noexcept(false),
  • Sınıfınızı, yıkıcısını zaten belirten başka bir sınıftan miras alın noexcept(false).
  • Sınıfınıza yıkıcısını zaten belirten statik olmayan bir veri üyesi koyun noexcept(false).

Son olarak, yıkıcıya atmaya karar verirseniz, her zaman bir çift istisna riskinin farkında olmalısınız (bir istisna nedeniyle yığın gevşetilirken fırlatma). Bu bir çağrıya neden olur std::terminateve nadiren istediğiniz şeydir. Bu davranışı önlemek için kullanarak yeni bir tane atmadan önce zaten bir istisna olup olmadığını kontrol edebilirsiniz std::uncaught_exception().


6

Diğer herkes yıkıcıları atmanın neden korkunç olduğunu açıkladı ... bu konuda ne yapabilirsiniz? Başarısız olabilecek bir işlem yapıyorsanız, temizleme gerçekleştiren ve keyfi istisnalar atabilecek ayrı bir genel yöntem oluşturun. Çoğu durumda, kullanıcılar bunu görmezden gelir. Kullanıcılar temizlemenin başarısını / başarısızlığını izlemek isterse, açık temizleme yordamını çağırabilirler.

Örneğin:

class TempFile {
public:
    TempFile(); // throws if the file couldn't be created
    ~TempFile() throw(); // does nothing if close() was already called; never throws
    void close(); // throws if the file couldn't be deleted (e.g. file is open by another process)
    // the rest of the class omitted...
};

Bir çözüm arıyorum ama ne olduğunu ve nedenini açıklamaya çalışıyorlar. Sadece yıkıcı içinde kapat fonksiyonunun çağırıldığını netleştirmek ister misiniz?
Jason Liu

5

İyi, kapsamlı ve doğru olan ana cevaplara ek olarak, referans olarak yazdığınız - "yıkıcılara istisna atmak o kadar da kötü değil" yazan makale hakkında yorum yapmak istiyorum.

Makale "istisnaları atmanın alternatifleri nelerdir" satırını alır ve alternatiflerin her biriyle ilgili bazı sorunları listeler. Bunu yaptıktan sonra, problemsiz bir alternatif bulamadığımız için istisnalar atmaya devam etmemiz gerektiği sonucuna varır.

Sorun şu ki, alternatiflerle listelediği sorunların hiçbiri, istisna davranışı kadar kötü bir yer değildir, ki hatırlayalım, "programınızın tanımlanmamış davranışı" dır. Yazarın itirazlarından bazıları "estetik olarak çirkin" ve "kötü tarzı teşvik eder". Şimdi hangisini tercih ederdiniz? Kötü tarzı olan bir program mı, yoksa tanımsız davranış sergileyen bir program mı?


1
Tanımlanmamış davranış değil, hemen fesih.
Marc van Leeuwen

Standart 'tanımsız davranış' diyor. Bu davranış genellikle sonlandırılır, ancak her zaman değildir.
DJClayworth

Hayır, İstisna yönetimi -> Özel işlevler bölümünde (standart kopyamda 15.5.1'dir, ancak numaralandırması muhtemelen eskidir) [hariç.
Marc van Leeuwen

2

S: Öyleyse sorum şu: Eğer bir yıkıcıdan atmak tanımsız davranışla sonuçlanırsa, bir yıkıcı sırasında meydana gelen hataları nasıl ele alırsınız?

C: Birkaç seçenek vardır:

  1. Başka yerlerde olup bitenlerden bağımsız olarak istisnaların yıkıcıdan akmasına izin verin. Ve bunu yaparken std :: terminate'in takip edebileceğinin farkında olun (hatta korkmayın).

  2. Hiçbir zaman istisnanın yıkıcıdan akmasına izin vermeyin. Bir günlüğe yazmak olabilir, bazı büyük kırmızı kötü metin varsa.

  3. benim fave : Eğer std::uncaught_exceptionyanlış döndürürse, istisnalar akmasına izin. True değerini döndürürse, günlüğe kaydetme yaklaşımına geri dönün.

Ama d'tors'a atmak iyi mi?

Yukarıdakilerin çoğuna katılıyorum yıkıcıda olabildiğince en iyi şekilde atmaktan kaçınılır. Ancak bazen bunun olabileceğini kabul etmek ve iyi idare etmek en iyisidir. Yukarıda 3 tane seçerdim.

Bir yıkıcıdan atmak için harika bir fikir olduğu birkaç garip durum var . "Kontrol etmelisiniz" hata kodu gibi. Bu, bir işlevden döndürülen bir değer türüdür. Arayan içerilen hata kodunu okur / kontrol ederse, döndürülen değer sessizce yok olur. Ancak , döndürülen hata kodu, dönüş değerleri kapsam dışına çıktığında okunmazsa, yıkıcısından bir istisna atar .


4
Kişisel fave Geçenlerde denedim bir şey olduğunu ve size gereken çıkıyor değil bunu. gotw.ca/gotw/047.htm
GManNickG

1

Şu anda (çoğu kişinin söylediği gibi) sınıfları aktif olarak yıkıcılarından istisnalar atmaması, bunun yerine başarısız olabilecek işlemi gerçekleştirmek için genel bir "kapat" yöntemi sağlaması gerektiği politikasını takip ediyorum ...

... ama bir vektör gibi konteyner tipi sınıflar için yıkıcıların içerdikleri sınıflardan atılan istisnaları maskelememesi gerektiğine inanıyorum. Bu durumda, aslında kendini yinelemeli olarak çağıran bir "serbest / kapalı" yöntemi kullanıyorum. Evet, özyineli olarak dedim. Bu deliliğin bir yöntemi var. İstisna yayılımı bir yığın olmasına dayanır: Tek bir istisna oluşursa, hem kalan yıkıcılar yine de çalışır ve rutin döndükten sonra bekleyen istisna yayılır, bu da harikadır. Birden fazla istisna meydana gelirse, (derleyiciye bağlı olarak) ya ilk istisna yayılır ya da program sonlanır, ki bu tamamdır. Özyinelemenin yığının üzerine taşmasıyla ilgili çok fazla istisna varsa, bir şey ciddi bir şekilde yanlıştır ve birisi bunu öğrenir, bu da tamamdır. Şahsen,

Mesele şu ki, konteynır nötr kalıyor ve yıkıcılarından istisnalar atmakla ilgili olarak davranıp davranmadıklarına veya kötü davranıp davranmadığına karar vermek içerdiği sınıflara kalmış.


1

İstisna atmanın nesne oluşturmanın başarılı olduğunu belirtmenin yararlı bir yolu olabileceği kurucuların aksine, istisnalar yıkıcılara atılmamalıdır.

Sorun, yığın çözme işlemi sırasında bir yıkıcıdan bir özel durum atılırsa oluşur. Bu durumda, derleyici, yığın çözme işlemine devam edip etmeyeceğini veya yeni özel durumu işleyip işlemediğini bilmediği bir duruma getirilir. Sonuç olarak programınız derhal sonlandırılır.

Sonuç olarak, en iyi eylem şekli, yıkıcılarda istisnaları tamamen kullanmaktan kaçınmaktır. Bunun yerine günlük dosyasına bir mesaj yazın.


1
Günlük dosyasına mesaj yazmak bir istisnaya neden olabilir.
Konard

1

Martin Ba (yukarıda) doğru yolda - mimar RELEASE ve COMMIT mantığı için farklı.

Sürüm için:

Herhangi bir hata yemelisin. Belleği boşaltıyorsunuz, bağlantıları kapatıyorsunuz, vs. Sistemdeki başka hiç kimse bunları bir daha GÖRMEMELİ ve kaynakları işletim sistemine geri veriyorsunuz. Burada gerçek hata işlemeye ihtiyacınız var gibi görünüyorsa, muhtemelen nesne modelinizdeki tasarım kusurlarının bir sonucudur.

Taahhüt için:

Std :: lock_guard gibi şeylerin muteksler için sağladığı aynı tür RAII sarmalayıcı nesnelerini istediğiniz yer burasıdır. Bunlarla taahhüt mantığını AT ALL dtor'a koymuyorsunuz. Bunun için özel bir API'niz var, daha sonra RAII'yi THEIR dtors'da işleyecek ve hataları burada ele alacak sarıcı nesneler var. Unutmayın, bir yıkıcıda istisnalar YAKALAYABİLİRSİNİZ; bu onları öldürücüdür. Bu aynı zamanda yalnızca farklı bir sarmalayıcı (örn. Std :: unique_lock vs. std :: lock_guard) oluşturarak ilke ve farklı hata işlemeleri uygulamanızı sağlar ve yalnızca yarı yol olan kesin mantığı çağırmayı unutmamanızı sağlar. 1. sırada bir dtor koymak için iyi bir gerekçe.


1

Benim sorum şu - eğer bir yıkıcıdan atmak tanımsız davranışla sonuçlanırsa, bir yıkıcı sırasında meydana gelen hataları nasıl ele alırsınız?

Ana sorun şudur: Başarısız olamaz . Ne de olsa başarısız olmak ne demektir? Veritabanına işlem yapmak başarısız olursa ve başarısız olursa (geri alınamazsa), verilerimizin bütünlüğüne ne olur?

Yıkıcılar hem normal hem de istisnai (başarısız) yollar için çağrıldıklarından, kendileri başarısız olamazlar ya da "başarısız başarısız oluruz".

Bu kavramsal olarak zor bir sorundur, ancak çoğu zaman çözüm, başarısızlığın başarısız olamayacağından emin olmak için bir yol bulmaktır. Örneğin, bir veritabanı bir dış veri yapısına veya dosyasına bağlanmadan önce değişiklikler yazabilir. İşlem başarısız olursa, dosya / veri yapısı atılabilir. Bundan sonra tek yapması gereken, o dış yapıdan / dosyadan değişiklikleri yerine getiremeyen bir atomik işlemin gerçekleştirilmemesidir.

Pragmatik çözüm belki de başarısızlık konusunda başarısız olma şansının astronomik olarak imkansız olduğundan emin olmaktır, çünkü bazı durumlarda başarısızlık yapmayı imkansız hale getirmek neredeyse imkansız olabilir.

Benim için en uygun çözüm, temizleme olmayan mantığınızı, temizleme mantığı başarısız olmayacak şekilde yazmaktır. Örneğin, mevcut bir veri yapısını temizlemek için yeni bir veri yapısı oluşturmak istiyorsanız, belki de bu yardımcı yapıyı önceden bir yıkıcı içinde yaratmamız gerekmeyecek şekilde yaratmayı deneyebilirsiniz.

Bu, söylenenden çok daha kolay, ama bunu yapmak için gördüğüm tek gerçekten doğru yol. Bazen normal yürütme yolları için istisnai yollardan ayrı bir yıkıcı mantık yazma yeteneği olması gerektiğini düşünüyorum, çünkü bazen yıkıcılar her ikisini de ele almaya çalışarak sorumlulukları iki katına çıkarmış gibi hissediyorlar (bir örnek açık işten çıkarma gerektiren kapsam korumaları ; eğer istisnai imha yollarını istisnai olmayan yollardan ayırabilirlerse buna ihtiyaç duymazlar).

Yine de nihai sorun, başarısız olamayız ve her durumda mükemmel bir şekilde çözülmesi zor bir kavramsal tasarım problemidir. Birbiri ile etkileşen tonlarca ufacık nesne ile karmaşık kontrol yapılarına çok fazla sarılmazsanız ve tasarımlarınızı biraz daha hantal bir şekilde modellerseniz (örnek: tüm parçacığı yok etmek için bir yıkıcı ile parçacık sistemi) daha kolaylaşır sistem, parçacık başına ayrı bir önemsiz yıkıcı değil). Tasarımlarınızı bu tür daha kaba düzeyde modellediğinizde, uğraşmak için önemsiz daha az yıkıcıya sahip olursunuz ve ayrıca yıkıcılarınızın başarısız olamayacağından emin olmak için gereken bellek / işleme yükünü de ödeyebilirsiniz.

Doğal olarak en kolay çözümlerden biri de yıkıcıları daha az kullanmaktır. Yukarıdaki parçacık örneğinde, belki de bir parçacığı yok ettikten / çıkardıktan sonra, hangi nedenle olursa olsun başarısız olabilecek bazı şeyler yapılmalıdır. Bu durumda, bu tür bir mantığı, istisnai bir yolda yürütülebilen parçacığın dtor'undan çağırmak yerine, parçacığı kaldırdığında parçacık sistemi tarafından her şeyin yapılmasını sağlayabilirsiniz . Bir parçacığın çıkarılması her zaman istisnai olmayan bir yol sırasında yapılabilir. Sistem yok edilirse, belki sadece tüm parçacıkları temizleyebilir ve başarısız olabilecek tek tek parçacık çıkarma mantığı ile uğraşmazken, başarısız olabilecek mantık sadece bir veya daha fazla parçacığı giderirken parçacık sisteminin normal yürütülmesi sırasında yürütülür.

Önemsiz yıkıcılara sahip çok sayıda ufacık nesne ile uğraşmaktan kaçınırsanız, bunun gibi çözümler genellikle vardır. İstisna güvenliği olmanın neredeyse imkansız olduğu bir karmaşaya karışabileceğiniz yer, hepsi önemsiz olmayan dtorlara sahip birçok ufacık nesneye karıştığınız zamandır.

Nothrow / noexcept, onu belirten herhangi bir şey (temel sınıfının noexcept spesifikasyonunu devralması gereken sanal işlevler dahil) atabilecek herhangi bir şeyi çağırmaya çalıştıysa, aslında bir derleyici hatasına dönüştürülürse çok yardımcı olacaktır. Eğer istemeden bir fırlatıcı yazabilirsek, bu şekilde tüm bunları derleme zamanında yakalayabiliriz.


1
Yıkım şimdi başarısız mı?
curiousguy

Bence bir başarısızlık sırasında yıkıcıların çağrılması, bu başarısızlığın temizlenmesi için çağrıldığı anlamına geliyor. Bu nedenle, etkin bir istisna sırasında bir yıkıcı çağrılırsa, önceki bir hatadan temizlenmez.
user2445507

0

Bir alarm olayı ayarlayın. Genellikle alarm olayları, nesneleri temizlerken daha iyi hata bildirme biçimidir

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.