Bir 'dene… yakala… nihayet' yapısının 'nihayet' kısmı gerekli midir?


25

Bazı diller (örneğin, C ++ ve PHP'nin ilk sürümleri) yapının bir finallybölümünü desteklemez try ... catch ... finally. Mı finallygerekli hiç? İçindeki kod her zaman çalıştığından, neden bu kodu try ... catchbir finallycümleciği olmayan bir bloktan sonra yerleştiremem / etmemeliyim ? Neden bir tane kullanmalı? (Kullanmak / kullanmamak için bir neden / motivasyon arıyorum finally, 'catch' ile uğraşmak için bir neden veya bunun neden yasal olduğunu düşünüyorum.)


Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
maple_shaft

Yanıtlar:


36

Başkalarının söylediklerine ek olarak, catch yan tümcesine bir istisna atılması da mümkündür. Bunu düşün:

try { 
    throw new SomeException();
} catch {
    DoSomethingWhichUnexpectedlyThrows();
}
Cleanup();

Bu örnekte, Cleanup()işlev hiçbir zaman çalışmaz, çünkü yakalama yan tümcesinde bir istisna atılır ve çağrı yığınında bir sonraki en yüksek yakalama bunu yakalar. Sonunda bir blok kullanmak bu riski ortadan kaldırır ve önyükleme için kodu temizler.


4
Teoriye girmeyen ve 'X dili Y'den daha iyidir' şeklinde kısa ve öz bir cevap için teşekkür ederiz.
Agi Çekiççi

56

Diğerlerinin de belirttiği gibi, tryolası her istisnayı yakalamadığınız sürece bir ifadeden sonra kodun çalıştırılacağının garantisi yoktur . Bu dedi ki:

try {
   mightThrowSpecificException();
} catch (SpecificException e) {
   handleError();
} finally {
   cleanUp();
}

1 olarak yeniden yazılabilir :

try {
   mightThrowSpecificException();
} catch (SpecificException e) {
   try {
       handleError();
   } catch (Throwable e2) {
       cleanUp();
       throw e2;
   }
} catch (Throwable e) {
   cleanUp();
   throw e;
}
cleanUp();

Ancak ikincisi, tüm işlenmeyen istisnaları yakalamanızı, temizleme kodunu çoğaltmanızı ve yeniden atmayı unutmayın. Yani gereklifinally değil , ama faydalı .

C ++ yok finallyçünkü Bjarne Stroustrup, RAII'nin daha iyi olduğuna inanıyor veya en azından çoğu durumda yeterli:

C ++ neden "sonunda" bir yapı sağlamıyor?

C ++ hemen hemen her zaman daha iyi olan bir alternatifi desteklediğinden: "Kaynak edinimi başlatma" tekniği (TC ++ PL3 bölüm 14.4). Temel fikir, yerel bir nesneyle bir kaynağı temsil etmektir, böylece yerel nesnenin yıkıcısı kaynağı serbest bırakacaktır. Bu şekilde, programcı kaynağı serbest bırakmayı unutamaz.


1 Tüm istisnaları yakalamak ve yığın izleme bilgilerini kaybetmeden tekrar taramak için özel kod dile göre değişir. İstisna oluşturulduğunda yığın izlemesinin yakalandığı Java'yı kullandım. C # 'da sadece kullanırsın throw;.


8
handleError()İkinci durumda da İstisnaları yakalamak zorundasın , değil mi?
Juri Robl

1
Ayrıca bir hata atıyor olabilirsiniz. Bunu bir kez daha catch (Throwable t) {}denemek isterim .. ilk bloğun etrafındaki catch bloğunu ( handleErroraynı zamanda fırlatılabilir maddeleri yakalamak için )
njzk2

1
Aslında çağırırken ekleyeceğiniz fazladan teşhisi eklerdim, handleErro();bu da nihayet blokların niçin yararlı olduğu konusunda daha iyi bir argüman oluşturacaktır (orijinal soru olmasa da).
Alex

1
Bu cevap, C ++ 'ın neden sahip olmadığı sorusunu ele almıyor finally, ki bu çok daha farklı.
DeadMG

1
@AgiHammerthief İç içe geçmiş özel istisna için tryiçindedir . İkincisi, istisnayı inceleyene kadar hatayı başarıyla idare edip edemeyeceğinizi veya istisna sebebinin aynı zamanda hatayı işlemenizi engellediğini (en azından bu seviyede) bilmemeniz mümkündür. G / Ç yaparken bu oldukça yaygındır. Yeniden deneme, orada çalışmayı garanti etmenin tek yolunun her şeyi yakalamak olduğu , ancak orijinal kodun, bloktan kaynaklanan istisnaların yukarı doğru ilerlemesine izin vereceği için var. catchcleanUpcatch (SpecificException e)
Doval,

22

finally bloklar genellikle birden fazla iade ifadesi kullanırken okunabilirliğe yardımcı olabilecek kaynakları temizlemek için kullanılır:

int DoSomething() {
    try {
        open_connection();
        return get_result();
    }
    catch {
        return 2;
    }
    finally {
        close_connection();
    }
}

vs

int DoSomething() {
    int result;
    try {
        open_connection();
        result = get_result();
    }
    catch {
        result = 2;
    }
    close_connection();
    return result;
}

2
Bence bu en iyi cevap. Sonunda genel bir istisna yerine geçmek üzere kullanmak çok boktan görünüyor. Doğru kullanım durumu, kaynakları veya benzer işlemleri temizlemek içindir.
Kik

3
Belki daha da yaygın olanı, catch bloğunun içinde değil, try bloğunun içine dönmektir.
Michael Anderson

Aklıma göre, kod kullanımını yeterince açıklamıyor finally. (Kodları, ikinci blokta olduğu gibi, çalıştığım yerde birden fazla iade ifadesi
bulunmadığı için kullanırdım

15

Anlaşılan tahmin ettiğiniz gibi, evet, C ++ bu mekanizma olmadan aynı yetenekleri sunar. Bu nedenle, kesinlikle konuşursak, try/ finallymekanizma gerçekten gerekli değildir.

Bununla birlikte, onsuz yapmak dilin geri kalanının nasıl tasarlandığına dair bazı zorunluluklar getirdiğini söyledi. C ++ 'da aynı eylemler bir sınıf yıkıcısına dahil edilmiştir. Bu öncelikle (sadece?) İşe yarıyor, çünkü C ++ 'daki yıkıcı çağırma deterministik. Bu da, bazıları kesinlikle sezgisel olmayan nesnelerin yaşamları hakkında oldukça karmaşık kurallara yol açıyor.

Diğer dillerin çoğu bunun yerine bir tür çöp toplama biçimi sağlar. Çöp toplama ile ilgili tartışmalı şeyler olsa da (örneğin, diğer bellek yönetimi yöntemlerine göre etkinliği) bir şey genellikle değildir: bir nesnenin doğrudan çöp toplayıcı tarafından "temizleneceği" zaman tam olarak bağlı değildir nesnenin kapsamına. Bu, temizleme işleminin ya doğru işlem için basit bir şekilde yapılması gerektiğinde belirleyici olması gerektiğinde veya temizleme işlemlerinin keyfi olarak ertelenmeyeceği kadar değerli kaynaklarla uğraşırken kullanımını engeller. try/ finallybu dillerin bu deterministik temizliği gerektiren durumlarla başa çıkmalarını sağlar.

Sanırım bu yetenek için C ++ sözdiziminin Java'nınkinden daha az "dost" olduğunu iddia edenlerin bu noktayı kaçırdıklarını düşünüyorum. Daha da kötüsü, sözdiziminin çok ötesine geçen sorumluluk dağılımı konusunda çok daha önemli bir noktayı kaçırıyorlar ve kodun nasıl tasarlandığı ile daha çok ilgisi var.

C ++ 'da, bu deterministik temizleme, nesnenin yıkıcısında gerçekleşir. Bu, nesnenin kendisinden sonra temizlemek için tasarlanabileceği (ve normal olarak tasarlanması gerektiği) anlamına gelir. Bu, nesneye yönelik tasarımın özüne gider - bir soyutlama sağlamak ve kendi değişmezlerini uygulamak için bir sınıf tasarlanmalıdır. C ++ 'da, kişi tam olarak bunu yapar - ve sağladığı değişmezlerden biri, nesne tahrip edildiğinde, o nesne tarafından kontrol edilen kaynakların (hepsi sadece hafıza değil) doğru şekilde imha edileceğidir.

Java (ve benzeri) biraz farklıdır. finalizeTeorik olarak benzer yetenekler sağlayabilecek bir şeye destek verirlerse de , destek o kadar zayıftır ki, temelde kullanılamaz (ve aslında, hiç kullanılmamış).

Sonuç olarak, sınıfın kendisi gerekli temizliği yapabilmek yerine, sınıfın müşterisinin bunu yapmak için adımlar atması gerekir. Eğer yeterince kısa görüşlü bir karşılaştırma yaparsak, ilk bakışta bu farkın oldukça küçük olduğu ve Java'nın bu konuda C ++ ile oldukça rekabetçi olduğu anlaşılabilir. Bunun gibi bir şeyle sonuçlanır. C ++ 'da sınıf şöyle görünür:

class Foo {
    // ...
public:
    void do_whatever() { if (xyz) throw something; }
    ~Foo() { /* handle cleanup */ }
};

... müşteri kodu şöyle görünür:

void f() { 
    Foo f;
    f.do_whatever();
    // possibly more code that might throw here
}

Java'da nesnenin sınıfta biraz daha az kullanıldığı yerlerde biraz daha fazla kod paylaşıyoruz. Bu başlangıçta oldukça eşit bir takas gibi görünüyor. Gerçekte, en tipik kodda sadece sınıfı tanımlamak, çünkü uzak olsa ondan var bir yerde, ama biz kullanmak o birçok yerde. C ++ yaklaşımı, temizliği tek bir yerde ele almak için bu kodu yazdığımız anlamına gelir. Java yaklaşımı, temizliği işlemek için birçok kez, birçok yerde - o sınıfın bir nesnesini kullandığımız her yerde bu kodu yazmamız gerektiği anlamına gelir.

Kısacası, Java yaklaşımı temel olarak sağlamaya çalıştığımız birçok soyutlamanın "sızdıran" olduğunu garanti eder - belirleyici temizlik gerektiren her sınıf, sınıfın müşterisine ne temizlemenin ayrıntıları ve temizlemenin nasıl yapılacağı hakkında bilgi sahibi olmasını zorunlu kılar. , bu detayların sınıfta gizli kalması yerine.

Yukarıda "Java yaklaşımı" olarak adlandırdığım halde, try/ finallyve diğer isimler altındaki benzer mekanizmalar tamamen Java ile sınırlı değildir. Öne çıkan bir örnek için, .NET dillerinin çoğu (tümü?) (Örneğin, C #) aynı şeyi sağlar.

Hem Java hem de C # 'nın en son yinelemeleri, aynı zamanda "klasik" Java ve C ++ arasında bu noktada bir yarım nokta noktası sağlar. C # 'da, temizleme işlemini otomatikleştirmek isteyen bir nesne, (en azından belli belirsiz) bir C ++ yıkıcısına benzer IDisposablebir Disposeyöntem sağlayan arayüzü uygulayabilir . Bu , Java'daki bir try/ finallylike aracılığıyla kullanılabilse de , C # , bir kapsam girilip oluşturulacak ve kapsamdan çıkıldığında yok edilecek kaynakları tanımlamanıza izin veren bir açıklama ile görevi biraz daha otomatik hale getirir using. Her ne kadar C ++ tarafından sağlanan otomasyon seviyesi ve kesinliği hala yetersiz olsa da, bu yine de Java üzerinde kayda değer bir gelişmedir. Özellikle, sınıf tasarımcısı nasıl detaylarını merkezileştirebilir.Sınıfın uygulanmasında elden çıkarılması IDisposable. İstemci programcısı için geriye kalan tek usingşey, IDisposablearayüzün olması gerektiği zaman kullanılacağından emin olmak için bir ifade yazmak için daha az yük . Java 7 ve daha yeni sürümlerinde, suçluyu korumak için adlar değiştirildi, ancak temel fikir temelde aynı.


1
Mükemmel cevap. Yıkıcılar olan C ++ olmazsa olmaz özellik.
Thomas Eding

13

Bunu başka kimsenin yetiştirmediğine inanamıyorum (amaçlanan bir püf noktası değil) - bir akıl yürütme şartına ihtiyacınız yok !

Bu tamamen mantıklı:

try 
{
   AcquireManyResources(); 
   DoSomethingThatMightFail(); 
}
finally 
{
   CleanUpThoseResources(); 
}

Hiçbir yerde hiçbir manşet cümlesi görünmez, çünkü bu yöntem bu istisnalar için yararlı bir şey yapamaz ; bir işleyici buna çağrı yığını yukarı doğru ilerletilmesi arkasına bırakılan kutu . Her yöntemde istisnaları yakalamak ve yeniden atmak, özellikle de aynı istisnayı yeniden atıyorsanız, Kötü bir fikirdir. Structured Exception Exception Handling'in nasıl çalışması gerektiği ile tamamen çelişiyor (ve her istisnadan bir "hata kodunu", sadece bir İstisna "şeklinde" döndürmeye oldukça yakın).

Bu yöntemi nedir gelmez yapmak zorunda olsa da, bu "Dış Dünya" asla kendisini var karışıklıktan hakkında hiçbir şey bilmek gerekiyor ki, kendisi sonra temizlemek için. Nihai madde tam da bunu yapar - çağrılan yöntemlerin nasıl davrandığı önemli değil, nihayet madde, yöntemin "çıkış yolunda" yürütülecektir (ve İstisna'nın atıldığı nokta arasındaki her nihayet maddesi için de geçerlidir. Sonunda onu ele geçiren catch yan tümcesi); her biri "gevşetin" çağrı yığını olarak çalıştırılır.


9

Beklemiyorsanız ve istisna atıldığında ne olurdu. Deneme, ortasından çıkar ve herhangi bir catch cümlesi çalıştırılmaz.

Son blok, buna yardımcı olmak ve temizliğin gerçekleşeceği istisna ne olursa olsun sağlamaktır.


4
Bu finally, 'beklenmedik' istisnaları catch(Object)veya catch(...)hepsini yakalama durumlarını önleyebileceğiniz için , bunun için yeterli bir neden değildir .
MSalters

1
Kulağa iş gibi geliyor. Kavramsal olarak nihayet daha temiz. Rağmen nadiren kullanmak itiraf etmeliyim.
Çabucak_ancak

7

Bazı diller kendi yapıları için hem kurucuları hem de yıkıcıları sunar (örneğin, C ++ inanıyorum). Bu dillerle, genellikle finallybir yıkıcıda yapılanların çoğunu (tartışmalı bir şekilde) yapabilirsiniz . Gibi - bu dillerde - bir finallyfıkra gereksiz olabilir.

Yıkıcı içermeyen bir dilde (örneğin Java), finallymadde olmadan doğru temizleme yapmak zordur (hatta imkansızdır) . NB - Java’da bir finaliseyöntem var ancak çağrılacağının garantisi yok.


Yıkıcılar determinist olduğunda yıkıcıların temizlik kaynaklarına yardımcı olduğunu not etmek faydalı olabilir . Nesnenin ne zaman imha edileceğini ve / veya çöp toplanacağını bilmiyorsak, yıkıcılar yeterince güvenli değildir.
Morwenn

@Morwenn - İyi nokta. Java’ya atıfta bulunduğumda bununla ima ettim finaliseama şu anda yıkıcılar / finaller etrafındaki siyasi tartışmalara girmemeyi tercih ederim.
OldCurmudgeon

C ++ 'da yıkım deterministiktir. Otomatik bir nesneyi içeren kapsam çıktığında (örneğin yığından fırlatıldığında), yıkıcı olarak adlandırılır. (C ++ yalnızca öbek üzerinde değil, yığında nesneler ayırmanıza izin verir.)
Rob K

@RobK - Ve bu, finalisehem genişletilebilir bir tadı hem de oop benzeri bir mekanizma ile tam işlevselliğidir - finalisediğer dillerin mekanizmasıyla çok anlamlı ve karşılaştırılabilir .
OldCurmudgeon

1

Sonunda dene ve yakalamaya çalış, sadece paylaşan iki farklı şey var: "try" anahtar kelimesi. Şahsen bunu farklı görmek isterdim. Onları bir arada görmenizin nedeni, istisnaların bir "sıçrama" üretmesidir.

Ve en sonunda, programlama akışı atlasa bile kod çalıştırmak için tasarlanmıştır. Bunun bir istisna veya başka bir nedenden dolayı olup olmadığı. Bir kaynak edinmenin ve atlamalar konusunda endişelenmenize gerek kalmadan temizlendiğinden emin olmanın zarif bir yoludur.


3
.NET'te ayrı mekanizmalar kullanılarak uygulanırlar; Bununla birlikte, Java'da JVM tarafından tanınan tek yapı, doğrudan destekleyen try catchancak desteklemeyen bir desen olan "hata durumunda" ile eşdeğerdir try finally; Sonuncuyu kullanan kod, yalnızca ilkini kullanan kod kullanılarak finally, kodun yürütülmesi gerekebilecek tüm kod noktalarına bloğun içeriği kopyalanarak dönüştürülür .
supercat,

@supercat nice, Java hakkında ek bilgi için teşekkürler.
Pieter B

1

Bu soru, C ++ 'ı dil olarak belirtmediğinden, alternatiflerden biri olarak öne sürülen nesne imhası için farklı bir yaklaşım sergiledikleri için C ++ ve Java'nın bir karışımını ele alacağım.

Try-catch bloğundan sonra kod yerine bir sonuç bloğu kullanmanızın nedenleri

  • try bloğundan erken döndüğünüzde: Bunu düşün

    Database db = null;
    try {
     db = open_database();
     if(db.isSomething()) {
       return 7;
     }
     return db.someThingElse();
    } finally {
      if(db!=null)
        db.close();
    }
    

    ile karşılaştırıldığında:

    Database db = null;
    int returnValue = 0;
    try {
     db = open_database();
     if(db.isSomething()) {
       returnValue = 7;
     } else {
       returnValue = db.someThingElse();
     }
    } catch(Exception e) {
      if(db!=null)
        db.close();
    }
    return returnValue;
    
  • catch blok (lar) ından erken dönüyorsunuz: Karşılaştır

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      return 7;
    } catch (DBIsADonkeyException e ) {
      return 11;
    } finally {
      if(db!=null)
        db.close();
    }
    

    vs:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      if(db!=null) 
        db.close();
      return 7;
    } catch (DBIsADonkeyException e ) {
      if(db!=null)
        db.close();
      return 11;
    }           
    db.close();
    
  • İstisnaları yeniden yaz. Karşılaştırmak:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      throw convertToRuntimeException(e,"DB was wonkey");
    } finally {
      if(db!=null)
        db.close();
    }
    

    vs:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      if(db!=null)
        db.close();
      throw convertToRuntimeException(e,"DB was wonkey");
    } 
    if(db!=null)
      db.close();
    

Bu örnekler çok kötü görünmüyor ancak çoğu zaman etkileşime giren bu vakaların birçoğu ve oyunda birden fazla istisna / kaynak türü var. finallyKodunuzun karışık bir bakım kabusu haline gelmemesine yardımcı olabilir.

Şimdi C ++ 'da bunlar kapsam tabanlı nesnelerle işlenebilir. Ancak IMO bu yaklaşımın iki dezavantajı vardır 1. sözdizimi daha az dosttur. 2. Yapım sırasının yıkım sırasının tersi olması işleri daha az netleştirebilir.

Java'da ne zaman olacağını bilmediğinizden temizleme işlemini yapmak için finalize etme yöntemini bağlayamazsınız - (iyi bir şekilde ancak eğlenceli yarış koşullarıyla dolu bir yol - JVM'nin ne zaman yok edeceğine karar vermede çok fazla kapsamı var. işler - genellikle beklediğiniz zaman değil - beklediğinizden daha erken veya daha geç - ve hot-spot derleyicisi ... iç çekmeye başladıkça değişebilir ...)


1

Bir programlama dilinde "gerekli" mantıklı olan tek şey talimatlardır:

assignment a = b
subtract a from b
goto label
test a = 0
if true goto label

Herhangi bir algoritma sadece yukarıdaki talimatları kullanarak gerçekleştirilebilir, diğer tüm dil yapıları programların yazılmasını kolaylaştıracak ve diğer programcılara daha anlaşılır hale getirecek şekilde hazırlanmıştır.

Böyle bir minimum komut seti kullanarak gerçek donanım için eski dünya bilgisayarına bakın .


1
Cevabınız kesinlikle doğrudur, fakat montajda kod yazmam; çok acı verici. Neden bir özellik kullanıyorum diye soruyorum, onu destekleyen dillerin ne anlama geldiğini anlamadım, bir dilin minimum talimat setinin ne olduğu değil.
Agi Çekiççi

1
Mesele şu ki, sadece bu 5 işlemi uygulayan herhangi bir dil, herhangi bir algoritmayı uygulayabiliyor; Amaç yalnızca bir algoritma uygulamaksa, çoğu dilde / yüksek seviyedeki dillerde operatörler "gerekli" değildir. Amaç, okunabilir bakım kodunun hızlı bir şekilde geliştirilmesiyse, çoğu gereklidir ancak “okunabilir” ve “bakım yapılabilir” ölçülebilir ve son derece öznel değildir. Güzel dil geliştiricileri pek çok özellik içeriyor: bazıları için bir kullanımınız yoksa bunları kullanmayın.
James Anderson,

0

Aslında benim için en büyük boşluk genellikle destekli finallyfakat yıkıcı olmayan dillerde. Çünkü , "temizlik" ile ilişkili tüm mantığı, yıkıcılar ile merkezi bir seviyede temizlemeyle uğraşmadan modüle edebilirsiniz. ilgili her fonksiyonda mantık. C # veya Java kodunun, mutekselerin kilidini manuel olarak açmak ve finallybloklardaki dosyaları kapatmak gibi şeyler gördüğümde , hepsi bu sorumluluğu ortadan kaldıran şekillerde yıkıcılar aracılığıyla otomatik hale getirildiğinde C kodu gibi modası geçmiş ve kendini hissettiriyor.

Bununla birlikte, eğer C ++ eklenmişse finallyve bunun da iki çeşit temizlik olduğu için hafif bir kolaylık bulabilirim :

  1. Yerel kaynakları yok etme / serbest bırakma / açma / kapama / vb. (Yıkıcılar bunun için mükemmeldir).
  2. Dış yan etkileri geri alma / geri alma (yıkıcılar bunun için yeterlidir).

İkincisi, en azından kaynak imhası fikrine sezgisel olarak eşleşmiyor, ancak işlenmeden önce imha edildiklerinde değişiklikleri otomatik olarak geri alan kapsam korumaları ile gayet iyi yapabilirsiniz. Orada finallytartışmasız en azından bir sağlar hafifçe (sadece bir ufacık azar) kapsam korumaları daha iş için daha basit bir mekanizma.

Bununla birlikte, daha basit bir mekanizma daha rollbackönce hiçbir dilde hiç görmediğim bir blok olacaktır . İstisna işlemeyi içeren bir dil tasarladıysam, bu benim için hayal kırıklığı yaratıyor. Buna benzer:

try
{
    // Cause external side effects. These side effects should
    // be undone if we don't finish successfully.
}
rollback
{
    // Reverse external side effects. This block is *only* executed 
    // if the 'try' block above faced a premature return out 
    // of the function. It is different from 'finally' which 
    // gets executed regardless of whether or not the function 
    // exited prematurely. This block *only* gets executed if we 
    // exited prematurely from  the try block so that we can undo 
    // whatever side effects it failed to finish making. If the try 
    // block succeeded and didn't face a premature exit, then we 
    // don't want this block to execute.
}

Bu, yan etki geri almalarını modellemenin en kolay yoludur, yıkıcılar yerel kaynak temizliği için mükemmel bir mekanizmadır. Şimdi sadece kapsam koruma çözümünden birkaç ekstra kod satırı kurtarıyor, ancak bununla birlikte bir dil görmek istememin nedeni, yan etki geri dönüşünün istisnaların ele alınmasının en ihmal edilmiş (ama en zor) yönü olma eğiliminde olmasıdır. Değişebilirlik etrafında dönen dillerde. Bu özelliğin, geliştiricilerin, işlevler yan etkilere neden olduğunda ve tamamlanamadığında işlemlerin doğru şekilde geri alınması açısından doğru şekilde ele alınması konusunda doğru şekilde düşünmeye teşvik edeceğini ve insanların doğru geri dönüş yapmanın ne kadar zor olabileceğini görünce bir yan bonus olarak kullanacağını düşünüyorum. her şeyden önce yan etkileri olmayan daha fazla fonksiyon yazmayı tercih edebilirler.

Ayrıca ne zaman çıkacağına bakılmaksızın, bir zaman damgası kaydediyormuş gibi, bir işlevden çıktıktan bağımsız olarak çeşitli şeyleri yapmak istediğiniz bazı karanlık durumlar da vardır. İş finallyiçin tartışmasız en basit ve mükemmel çözüm var, çünkü nesneyi yalnızca yıkıcıyı yalnızca bir zaman damgasını kaydetmek amacıyla kullanmak amacıyla kullanmak için başlatmaya çalışmak gerçekten çok garip geliyor (her ne kadar ince ve lambdalarla çok iyi bir şekilde yapsanız bile) ).


-9

C ++ dili ile ilgili pek çok sıra dışı şey gibi, bir try/finallyyapının eksikliği de bir tasarım hatasıdır, buna sık sık hiç gerçek bir tasarım çalışması yapmamış gibi görünen bir dilde bile diyebilirsiniz .

RAII (temizlik için istif tabanlı nesneler üzerinde kapsam tabanlı deterministik yıkıcı kullanımının kullanılması) iki ciddi kusur vardır. Birincisi , Liskov Değişim Prensibi'ni ihlal eden bir uyuşukluk olan yığın tabanlı nesnelerin kullanımını gerektirmesidir . C ++ 'dan önce veya sonra başka hiçbir OO dilinin kullanılmamasının birçok nedeni vardır - epsilon içinde; D, ağır bir şekilde C ++ 'a dayandığı ve hiçbir şekilde pazar payına sahip olmadığı için sayılmaz - ve neden oldukları sorunları açıklamak bu cevabın kapsamı dışındadır.

İkincisi, finallyyapılabilecek şey, nesne imha üstünlüğüdür. RAII ile C ++ 'da yapılanların çoğu, çöp toplamayan Delphi dilinde şu şekilde tanımlanır:

myObject := MyClass.Create(arguments);
try
   doSomething(myObject);
finally
   myObject.Free();
end;

Bu açık yapılmış RAII modelidir; Yukarıdaki birinci ve üçüncü satırlara denk olan bir C ++ yordamı oluşturacak olsaydınız, derleyicinin üreteceği şey, temel yapısında yazdıklarım gibi görünmesini sağlardı. Ve try/finallyC ++ 'ın sağladığı yapıya tek erişim olduğundan , C ++ geliştiricileri oldukça miyop bir bakış açısına sahip olur try/finally: sahip olduğunuz tek şey bir çekiç olduğunda, tabiri caizse her şey bir yıkıcı gibi görünmeye başlar.

Ancak deneyimli bir geliştiricinin bir finallyyapıyla yapabileceği başka şeyler de var . Bu, bir istisna karşısında bile belirleyici imha ile ilgili değildir; Bu , bir istisna karşısında bile belirleyici kod yürütme ile ilgilidir .

İşte Delphi kodunda sıkça görebileceğiniz başka bir şey: Kullanıcı kontrollerine bağlı bir veri kümesi nesnesi. Veri kümesi harici bir kaynaktan gelen verileri tutar ve kontroller verilerin durumunu yansıtır. Veri kümenize bir demet veri yüklemek üzereyseniz, veri bağlamayı geçici olarak devre dışı bırakmak isteyeceksiniz, böylece kullanıcı arayüzünüzde garip şeyler yapmaz, girilen her yeni kayıtla tekrar tekrar güncellemeye çalışabilirsiniz , böylece şöyle kodlarsınız:

dataset.DisableControls();
try
   LoadData(dataset);
finally
   dataset.EnableControls();
end;

Açıkçası, burada yok edilmekte olan bir nesne yok ve birine gerek yok. Kod basit, özlü, açık ve etkilidir.

Bu C ++ 'da nasıl yapılır? İlk önce bütün bir sınıfı kodlamalısın . Muhtemelen denir DatasetEnablerya da bir şey şey olurdu . Tüm varlığı bir RAII yardımcısı olarak olurdu. O zaman böyle bir şey yapmanız gerekecek:

dataset.DisableControls();
{
   raiiGuard = DatasetEnabler(dataset);
   LoadData(dataset);
}

Evet, görünüşte gereksiz kıvrımlı kaşlı ayraçlar, uygun kapsamlamayı yönetmek ve veri kümesinin yöntemin sonunda değil derhal yeniden etkinleştirilmesini sağlamak için gereklidir. Öyleyse, neyle sonuçlandığınız daha az kod satırını almaz (Mısır ayraçları kullanmazsanız). Yükü fazla olan bir nesnenin yaratılması gerekiyor. (C ++ kodunun hızlı olması gerekiyor mu?) Açık değil, bunun yerine derleyici sihrine güveniyor. Yürütülen kod bu yöntemde hiçbir yerde açıklanmaz, ancak bunun yerine tamamen farklı bir dosyada , muhtemelen tamamen farklı bir dosyada bulunur . Kısacası, try/finallybloğu kendiniz yazabilmekten daha iyi bir çözüm olamaz .

Bu tür bir dil, dil tasarımında bunun için bir isim olduğu kadar yaygındır: soyutlama inversiyonu. Düşük seviyeli bir yapının üzerine yüksek seviyeli bir yapı inşa edildiğinde ortaya çıkar ve daha sonra düşük seviyeli yapı doğrudan dilde desteklenmez ; yüksek seviyeli yapı, çoğu zaman hem okunabilirliği hem de verimliliği kodlayan sarp cezalar.


Yorumlar, bir soruyu ve cevabı netleştirmek veya iyileştirmek içindir. Bu cevap hakkında bir tartışma yapmak istiyorsanız, lütfen sohbet odasına gidin. Teşekkür ederim.
maple_shaft
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.