Sonunda bir yıkıcı arasındaki kavramsal fark nedir?


12

Birincisi, C ++ 'da neden nihayet' yapı 'yok mu? ancak başka bir soru üzerine uzun süren bir yorum tartışması ayrı bir soru gerektirmektedir.

Dışında sayısından o finallyC # ve Java yapabilirsiniz temelde kapsam başına sadece bir kez (== 1) mevcut ve tek bir kapsam birden (== n) C ++ yıkıcı olabilir, onlar aslında aynı şey olduğunu düşünüyorum. (Bazı teknik farklılıklarla.)

Ancak, başka bir kullanıcı şunu savundu :

... bir dtorun doğal olarak (Release sematics) için bir araç olduğunu ve sonunda doğal olarak (Semantik taahhüt) için bir araç olduğunu söylemeye çalışıyordum. Nedenini görmüyorsanız: Nihayet bloklarda birbirlerinin ü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 olayıdır. Yıkıcılar verileri serbest bırakmak içindir, nihayet kontrolü serbest bırakmak içindir. Farklılar; C ++ 'ın onları birbirine bağlaması talihsiz bir durumdur.)

Birisi bunu temizleyebilir mi?

Yanıtlar:


6
  • İşlem ( try)
  • Hata Çıkışı / Yanıtı ( catch)
  • Harici Hata ( throw)
  • Programcı Hatası ( assert)
  • Geri alma (en yakın şey, onları yerel olarak destekleyen dillerde kapsam korumaları olabilir)
  • Kaynakları Serbest Bırakma (yıkıcılar)
  • Çeşitli İşlemlerden Bağımsız Kontrol Akışı ( finally)

finallyÇeşitli işlemden bağımsız kontrol akışından daha iyi bir açıklama bulunamaz . Bir işlem ve hata kurtarma zihniyeti bağlamında, özellikle hem yıkıcılara hem de teorik bir dilde, doğrudan herhangi bir üst düzey kavramla eşleşmesi gerekmez finally.

Bana göre en eksik olan şey, doğrudan dış yan etkileri geri alma kavramını temsil eden bir dil özelliğidir. D gibi dillerde kapsam muhafızları, düşünebildiğim en yakın şey, bu konsepti temsil etmeye yaklaşıyor. Bir kontrol akışı açısından bakıldığında, belirli bir işlevin kapsamındaki bir geri dönüşün olağandışı bir yolu normal bir yoldan ayırt etmesi gerekirken, işlem başarısız olursa işlevin neden olduğu herhangi bir yan etkinin dolaylı olarak geri alınmasını otomatikleştirir, ancak işlem başarılı olduğunda . Diyelim ki, succeededbir yıkıcıda geri alma mantığını önlemek için deneme bloğumuzun sonunda true değerine bir boolean ayarlarsak, yıkıcılarla yapmak yeterince kolaydır . Ama bunu yapmanın oldukça dolambaçlı bir yolu.

Bu çok fazla tasarruf etmeyecek gibi görünse de, yan etki tersine çevirme doğru yapılması en zor şeylerden biridir (örn: istisna açısından güvenli bir genel konteyner yazmayı zorlaştıran şey).


4

Bir şekilde - aynı şekilde, bir Ferrari ve bir transit, farklı kullanımlar için tasarlanmış olsalar bile, bir bardak süt için dükkanları aşağı çekmek için kullanılabilir.

Her kapsamda bir try / nihayet yapı yerleştirebilir ve bir C ++ yıkıcıya benzetmek için nihayet bloğundaki tüm kapsam tanımlı değişkenleri temizleyebilirsiniz. Bu, kavramsal olarak, C ++ 'ın yaptığı şeydir - bir değişken kapsam dışına çıktığında (yani kapsam bloğunun sonunda) derleyici otomatik olarak yıkıcıyı çağırır. Ancak denemenizi / nihayetinde denemenizi ayarlamanız gerekir, böylece deneme her kapsamda ilk ve son olarak en son şeydir. Ayrıca, her nesnenin, nihayet bloğunda arayacağınız durumunu temizlemek için kullandığı özel olarak adlandırılmış bir yönteme sahip olması için bir standart tanımlamanız gerekir, ancak sanırım dilinizin sağladığı normal bellek yönetimini bırakabilirsiniz. boşaltılan nesneyi istediği zaman temizleyin.

Yine de bunu yapmak güzel olmaz ve .NET IDispose'i manuel olarak yönetilen bir yıkıcı olarak tanıtmış olsa ve blokları bu manuel yönetimi biraz daha kolay hale getirmek için kullanmakla birlikte, pratikte yapmak istediğiniz bir şey değil. .


4

Benim bakış itibaren temel fark bir olmasıdır yıkıcı c ++ bir olan örtülü deneyin ... ederken ayrılan kaynakların serbest bırakılması için (otomatik olarak çağrılır) mekanizması nihayet bir olarak kullanılabilecek açık bunu mekanizma.

C ++ programlarında programcı tahsis edilmiş kaynakları serbest bırakmaktan sorumludur. Bu genellikle bir sınıfın yıkıcısında uygulanır ve bir değişken kapsam dışına çıktığında veya silme çağrıldığında hemen yapılır.

C ++ 'da, bir sınıfın yerel değişkeni, newbu örneklerin kaynakları kullanılmadan oluşturulduğunda , bir istisna olduğunda yıkıcı tarafından örtülü olarak serbest bırakılır.

// c++
void test() {
    MyClass myClass(someParameter);
    // if there is an exception the destructor of MyClass is called automatically
    // this does not work with
    // MyClass* pMyClass = new MyClass(someParameter);

} // on test() exit the destructor of myClass is implicitly called

Java, c # ve otomatik bellek yönetimine sahip diğer sistemlerde, sistem çöp toplayıcısı bir sınıf örneği yok edildiğinde karar verir.

// c#
void test() {
    MyClass myClass = new MyClass(someParameter);
    // if there is an exception myClass is NOT destroyed so there may be memory/resource leakes

    myClass.destroy(); // this is never called
}

Örtük bir mekanizma yoktur, bu yüzden bunu nihayet denemeyi kullanarak açıkça programlamanız gerekir.

// c#
void test() {
    MyClass myClass = null;

    try {
        myClass = new MyClass(someParameter);
        ...
    } finally {
        // explicit memory management
        // even if there is an exception myClass resources are freed
        myClass.destroy();
    }

    myClass.destroy(); // this is never called
}

C ++ 'da, yıkıcı neden istisna durumunda bir yığın nesnesiyle değil, yalnızca bir yığın nesnesiyle otomatik olarak çağrılır?
Giorgio

@Giorgio Çünkü yığın kaynakları doğrudan çağrı yığınına bağlı olmayan bir bellek alanında yaşıyor. Örneğin, 2 iplikle çok evreli bir uygulama hayal Ave B. Eğer bir evre atarsa, geri alma A'sişlemi tahsis edilen kaynakları tahrip etmemelidir B, örneğin - evre durumları birbirinden bağımsızdır ve yığın üzerinde yaşayan kalıcı bellek her ikisinden de bağımsızdır. Ancak, genellikle C ++ 'da, yığın bellek hala yığın üzerindeki nesnelere bağlıdır.

@Giorgio Örneğin, bir std::vectornesne yığın üzerinde yaşayabilir, ancak yığın üzerinde belleğe işaret edebilir - hem vektör nesnesi (yığın üzerinde) hem de içeriği (yığın üzerinde) bu durumda bir yığın gevşemesi sırasında yeniden yerleştirilir, çünkü yığın üzerinde vektörün yok edilmesi, ilgili hafızayı yığın üzerinde serbest bırakan bir yıkıcıyı harekete geçirir (ve aynı şekilde bu yığın elemanlarını yok eder). Genellikle istisna güvenliği için, yalnızca yığın üzerindeki belleğe işaret eden tutamaçlar olsa bile, yığın gevşemesinde hem yığın hem de yığın belleği serbest bırakma işlemini otomatikleştiren çoğu C ++ nesnesi yığın üzerinde yaşar.

4

Bunu bir soru olarak gönderdiğine sevindim. :)

Yıkıcıların ve finallykavramsal olarak farklı olduklarını söylemeye çalışıyordum :

  • Yıkıcılar kaynakları serbest bırakmak içindir ( veri )
  • finallyarayan kişiye geri dönmek içindir ( kontrol )

Diyelim ki, bu varsayımsal sözde kodu:

try {
    bar();
} finally {
    logfile.print("bar has exited...");
}

finallyburada kaynak yönetimi problemi değil, tamamen kontrol problemi çözülüyor.
Bunu çeşitli nedenlerden dolayı bir yıkıcıda yapmak mantıklı olmaz:

  • Hiçbir şey "edinilmiş" veya "yaratılmış" değildir
  • Günlük dosyasına yazdırmak için Başarısızlık olacak değil kaynak sızıntıları, veri bozulması, vb (burada günlük dosyası başka yerde programa geri beslenir olmadığını varsayarak) neden
  • Yıkmak meşru logfile.print, oysa yıkım (kavramsal olarak) başarısız olamaz

İşte bu sefer Javascript'teki gibi başka bir örnek:

var mo_document = document, mo;
function observe(mutations) {
    mo.disconnect();  // stop observing changes to prevent re-entrance
    try {
        /* modify stuff */
    } finally {
        mo.observe(mo_document);  // continue observing (conceptually, this can fail)
    }
}
mo = new MutationObserver(observe);
return observe();

Yukarıdaki örnekte, yine, serbest bırakılacak kaynak yoktur.
Aslında, finallyblok edilir edinme hedefine, potansiyel olarak başarısız olabilir ulaşmak için dahili olarak kaynakları. Bu nedenle, bir yıkıcı kullanmak mantıklı değildir (Javascript'te bir tane varsa).

Öte yandan, bu örnekte:

b = get_data();
try {
    a.write(b);
} finally {
    free(b);
}

finallybir kaynağı yok ediyor b. Bu bir veri sorunu. Sorun, kontrolü arayana temiz bir şekilde geri döndürmek değil, kaynak sızıntılarından kaçınmaktır.
Başarısızlık bir seçenek değildir ve (kavramsal olarak) asla meydana gelmemelidir.
Her sürümü bmutlaka bir satın alma işlemiyle eşleştirilir ve RAII kullanmak mantıklıdır.

Başka bir deyişle, ya ikisinin de aynı sorun olduğu anlamına gelmediği ya da her ikisinin de her ikisi için de uygun çözümler olduğu ya ikisini de simüle etmek için kullanabilmenizdir.


Teşekkürler. Kabul etmiyorum, ama hey :-) Önümüzdeki günlerde tam bir karşıt görüş yanıtı ekleyebileceğimi düşünüyorum ...
Martin Ba

2
Nasıl Aslında finallyçoğunlukla bu işe sürüm (non-bellek) kaynak faktörü için kullanılır?
Bart van Ingen Schenau

1
@BartvanIngenSchenau: Şu anda var olan herhangi bir dilin, tanımladığımla eşleşen bir felsefe veya uygulamaya sahip olduğunu asla iddia etmedim. İnsanlar henüz var olabilecek her şeyi icat etmeyi bitirmedi. Sadece farklı fikirleri ve farklı kullanım durumları olduğu için iki kavramı birbirinden ayırmanın bir değeri olacağını savundum. Merakınızı gidermek için D' nin her ikisine de sahip olduğuna inanıyorum . Muhtemelen başka diller de vardır. Yine de alakalı olduğunu düşünmüyorum ve neden Java'nın lehine olduğunu daha az umursamadım finally.
user541686

1
JavaScript'te karşılaştığım pratik bir örnek, uzun bir işlem sırasında (bir istisna oluşturabilir) fare işaretçisini geçici olarak bir kum saatine değiştiren ve daha sonra finallymaddede normale döndüren işlevlerdir . C ++ dünya görüşü, sözde küresel bir değişkene yapılan ödevin bu “kaynağını” yöneten bir sınıf tanıtacaktır . Bu hangi kavramsal mantıklı? Ancak yıkıcılar gerekli blok sonu kodu yürütmesi için C ++ 'ın çekici.
dan04

1
@ dan04: Çok teşekkürler, bunun için mükemmel bir örnek. Yemin edebilirim ki RAII'nin mantıklı olmadığı birçok durumla karşılaşırdım ama onları düşünürken çok zorlandım.
user541686

1

k3b'nin cevabı gerçekten güzelce ifade ediyor:

c ++ 'da bir yıkıcı, tahsis edilmiş kaynakları serbest bırakmak için örtülü bir mekanizmadır (otomatik olarak çağrılır), buna karşın try ... nihayet bunu yapmak için açık bir mekanizma olarak kullanılabilir .

"Kaynaklara" gelince, Jon Kalb'a atıfta bulunmaktan hoşlanıyorum : RAII Sorumluluk Alımının Başlatılması anlamına gelmelidir .

Her neyse, örtük ve açık olarak bu gerçekten öyle görünüyor:

  • Bir d'tor, bir nesnenin ömrü sona erdiğinde (genellikle kapsamın sonuna denk gelir) hangi işlemlerin (dolaylı olarak) gerçekleşeceğini tanımlayan bir araçtır.
  • Son olarak bir blok, kapsamın sonunda hangi işlemlerin yapılacağını açık bir şekilde tanımlamak için bir araçtır.
  • Ayrıca, teknik olarak, her zaman nihayet atmanıza izin verilir, ancak aşağıya bakın.

Bence bu kavramsal kısım için, ...


... şimdi IMHO'nun bazı ilginç detayları var:

Ayrıca c'tor / d'tor'un yıkıcıda bir kod çalıştırma sorumluluğu dışında kavramsal olarak bir şey "edinmesi" veya "yaratması" gerektiğini düşünmüyorum. Sonunda da ne yapar: bazı kod çalıştırın.

Ve nihayet bir bloktaki kod kesinlikle bir istisna atabilirken, benim için kavramsal olarak açık ve üstü örtülü olduklarını söylemek yeterli bir ayrım değildir.

(Artı, "iyi" kodun nihayet atması gerektiğine ikna olmadım - belki de bu kendi başına başka bir soru.)


Javascript örneğim hakkındaki düşüncelerin neler?
user541686

Diğer argümanlarınızla ilgili olarak: "Aynı şeyi gerçekten dikkate almadan kaydetmek ister miyiz?" Evet, bu sadece bir örnek ve bir nevi noktayı kaçırıyorsunuz ve evet, hiç kimsenin her vaka için daha spesifik ayrıntılar kaydetmesi yasaklandı. Buradaki nokta, hiçbir zaman , her ikisi için ortak olan bir şeyi günlüğe kaydetmek istediğiniz bir durum olmadığını kesinlikle iddia edemezsiniz . Bazı günlük girişleri genel, bazıları belirli; ikisini de istiyorsun. Ve yine, günlüğe odaklanarak noktayı tamamen kaçırıyorsunuz. 10 satırlı örnekleri motive etmek zordur; lütfen noktayı kaçırmamaya çalışın.
user541686

Bunlara asla hitap
etmediniz

@Mehrdad - Javascript örneğinize değinmedim, çünkü bunun hakkında ne düşündüğümü tartışmam başka bir sayfa alacaktı. (Denedim, ama atladığım tutarlı bir şeyi ifade etmek çok uzun sürdü :-)
Martin Ba

@Mehrdad - diğer noktalarınıza gelince - anlaşamadığımızı kabul etmemiz gerekiyor gibi görünüyor. Farkı nereye hedeflediğinizi görüyorum, ancak kavramsal olarak farklı bir şey olduğuna ikna olmadım: Temelde çoğunlukla kampta nihayet atmanın gerçekten kötü bir fikir olduğunu düşündüğüm için ( not : ben de senin düşünmek observerorada atma gerçekten kötü bir fikir olacağını örneğin.) Bu konuyu daha fazla tartışmak istiyorsanız, bir sohbet açmak için çekinmeyin. Argümanlarınızı düşünmek kesinlikle eğlenceliydi. Şerefe.
Martin Ba
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.