C ++, istisnaları daha sık kullanmayı tercih ediyor gibi görünüyor.
Bazı yönlerden aslında Objective-C'den daha azını öneriyorum çünkü C ++ standart kütüphanesi genellikle en yaygın durum tasarım formunda ( operator[]
yani,) rasgele erişim dizisinin sınır dışı erişimi gibi programcı hatalarını atmayacaktı veya geçersiz bir yineleyiciden vazgeçmeye çalışıyor. Dil, bir diziye sınırların dışında erişmeye veya boş bir işaretçiyi veya bu türden herhangi bir şeyi silmeyi bırakmaz.
Programcı hatalarını büyük ölçüde istisna işleme denkleminden çıkarmak aslında diğer dillerin sıklıkla yanıt verdiği çok büyük bir hata kategorisini ortadan kaldırır throwing
. C ++ assert
, bu tür durumlarda muhtemelen dil, bu tür çalışma zamanı denetimlerinin maliyetini dayatmak istemediğinden (sürüm / üretim derlemelerinde derlenmez, yalnızca hata ayıklama derlemeleri) veya yalnızca aksaklık (genellikle çökme) eğilimindedir. programcı, bu tür kontrolleri kendisi yapan bir kod yazarak maliyetleri ödemek istemedikçe, bu tür programcı hatalarını tespit etmek için gerekli olacaktır.
Sutter, C ++ Kodlama Standartlarında bu gibi durumlarda istisnalardan kaçınmayı bile teşvik eder:
Bir programlama hatasını bildirmek için bir istisna kullanmanın birincil dezavantajı, hata ayıklayıcının ihlalin tespit edildiği satırda satırın durumu bozulmadan başlatılmasını istediğinizde yığın gevşemesinin gerçekten olmasını istememenizdir. Özetle: Olabileceğini bildiğiniz hatalar var (bkz. 69 - 75. Maddeler). Olmaması gereken her şey için ve eğer varsa programcının hatası var assert
.
Bu kural zorunlu olarak taş değildir. Bazı kritik görevlerde, programcı hatalarının meydana geldiği yerlerde ve programcı hatalarının throw
varlığında geçersiz bir şeyi ertelemeye veya sınırların dışına erişmeye çalışmak gibi düzenli olarak kaydeden ambalajlayıcılar ve bir kodlama standardı kullanmak tercih edilebilir , çünkü yazılımın bir şansı varsa, bu durumlarda kurtarılamayacak kadar maliyetli olabilir. Ancak genel olarak, dilin daha yaygın kullanımı, programcı hatalarının önüne geçmemeyi tercih eder.
Dış İstisnalar
C ++ 'da en sık teşvik edilen istisnaları gördüğümde (örneğin, standart komiteye göre), program dışındaki bazı harici kaynaklarda beklenmedik bir sonuç olduğu gibi "harici istisnalar" içindir. Bir örnek bellek ayıramıyor. Bir diğeri, yazılımın çalışması için gerekli olan kritik bir dosyayı açamıyor. Başka bir sunucu gerekli bir sunucuya bağlanamıyor. Diğeri ise, ortak vaka yürütme yolu bu harici kesintinin olmaması durumunda başarılı olmasını bekleyen bir işlemi iptal etmek için bir iptal düğmesine basan bir kullanıcıdır. Bütün bunlar, anında yazılımın ve onu yazan programcıların kontrolü dışındadır. Operasyonun (kitabımda gerçekten bölünmez bir işlem olarak düşünülmesi gereken) başarılı olmasını engelleyen dış kaynaklardan beklenmedik sonuçlardır.
işlemler
Bir try
bloğa "işlem" olarak bakmayı teşvik ediyorum çünkü işlemler bir bütün olarak başarılı olmalı ya da bir bütün olarak başarısız olmalıdır. Bir şey yapmaya çalışıyorsak ve yarıda başarısız olursa, program durumuna yapılan herhangi bir yan etkinin / mutasyonun genellikle işlemin hiç yapılmadığı gibi geçerli bir duruma geri döndürülmesi için geri alınması gerekir, tıpkı bir sorguyu yarıya kadar işleyemeyen bir RDBMS'nin veritabanının bütünlüğünden ödün vermemesi gibi. Program durumunu doğrudan söz konusu işlemde değiştirirseniz, bir hatayla karşılaştığınızda "değiştirmemeniz" gerekir (ve burada kapsam korumaları RAII ile yararlı olabilir).
Daha basit alternatif isimli mutasyona yok orijinal program durumunu; bir kopyasını değiştirebilir ve ardından başarılı olursa kopyayı orijinali ile değiştirebilirsiniz (takasın alamayacağından emin olun). Başarısız olursa, kopyayı atın. Bu, genel olarak hata işleme için istisnalar kullanmasanız bile geçerlidir. Bir hata ile karşılaşmadan önce program durumu mutasyonları meydana gelmişse, bir "işlemsel" zihniyet uygun kurtarmanın anahtarıdır. Ya bir bütün olarak başarılı olur ya da bütünüyle başarısız olur. Mutasyonlarını yarıda başaramaz.
Bu, programcıların düzgün bir şekilde hata veya istisna işlemeyi nasıl yapacağını sorduğunu görünce tuhaf bir şekilde en az tartışılan konulardan biridir, ancak birçoğunda program durumunu doğrudan değiştirmek isteyen herhangi bir yazılımda doğru bir şekilde elde etmek en zor olanıdır. operasyonları. Saflık ve değişmezlik, iş parçacığı güvenliğine yardımcı oldukları ölçüde istisna güvenliği sağlamaya yardımcı olabilir, çünkü oluşmayan bir mutasyon / dış yan etkinin geri alınması gerekmez.
Verim
İstisnaların kullanılıp kullanılmayacağına dair bir başka yol gösterici performanstır ve bazı takıntılı, kuruş kısma, karşı üretken bir şekilde kastetmiyorum. Birçok C ++ derleyicisi "Sıfır Maliyet İstisnası İşleme" denen şeyi uygular.
Hatasız yürütme için sıfır çalışma zamanı ek yükü sunar, bu da C dönüş değeri hata işlemenin bile üstesinden gelir. Bir değiş tokuş olarak, bir istisnanın yayılmasının büyük bir yükü vardır.
Bu konuda okuduğum şeye göre, genel durum yürütme yollarınızın, olağanüstü yollara (özellikle C-tarzı hata kodu işleme ve yayılmasına eşlik eden ek yük bile değil) ek yük gerektirmemesine neden olur. yani throwing
artık her zamankinden daha pahalı).
"Pahalı" miktarını belirlemek biraz zordur, ancak yeni başlayanlar için muhtemelen bir milyon döngüyü sıkı bir döngüye atmak istemezsiniz. Bu tür tasarım, istisnaların her zaman solda ve sağda meydana gelmediğini varsayar.
Sigara Hatalar
Ve bu performans noktası beni hatalara götürüyor, bu da her türlü başka dile bakarsak şaşırtıcı derecede bulanık. Ancak, yukarıda belirtilen sıfır maliyetli EH tasarımı göz önüne alındığında, throw
bir sette bulunmayan bir anahtara yanıt olarak neredeyse kesinlikle istemediğinizi söyleyebilirim . Çünkü bu sadece bir hata değildir (anahtarı arayan kişi seti inşa etmiş ve her zaman var olmayan anahtarları aramayı beklemiş olabilir), ama bu bağlamda çok pahalı olurdu.
Örneğin, bir küme kesişme işlevi iki küme arasında geçiş yapmak ve ortak anahtarları aramak isteyebilir. Bir anahtar bulamazsanız threw
, yinelemelerin yarısında veya daha fazlasında istisnalarla karşılaşabilirsiniz:
Set<int> set_intersection(const Set<int>& a, const Set<int>& b)
{
Set<int> intersection;
for (int key: a)
{
try
{
b.find(key);
intersection.insert(other_key);
}
catch (const KeyNotFoundException&)
{
// Do nothing.
}
}
return intersection;
}
Yukarıdaki örnek kesinlikle saçma ve abartılı, ancak üretim kodunda, C ++ 'da istisnalar kullanan diğer dillerden gelen bazı insanlar böyle bir şey gördüm ve bunun istisnaların uygun bir kullanımı olmadığını makul olarak pratik bir ifade olarak görüyorum. C ++ 'da. Yukarıdaki başka bir ipucu, catch
bloğun kesinlikle yapacak hiçbir şeyi olmadığını ve sadece bu tür istisnaları zorla görmezden gelmek için yazıldığını göreceksiniz ve bu genellikle istisnaların muhtemelen C ++ 'da çok uygun bir şekilde kullanılmadığına dair bir ipucu (garantör olmasa da).
Bu tür durumlar için, başarısızlığı gösteren bir tür dönüş değeri ( false
geçersiz bir yineleyiciye dönmekten veya nullptr
bağlamda anlamlı olan herhangi bir şey ) genellikle çok daha uygundur ve ayrıca hata olmayan bir türden daha pratik ve üretkendir. durumda genellikle analog catch
bölgeye ulaşmak için bazı yığın çözme işlemi gerektirmez .
Sorular
İstisnalardan kaçınmayı seçersem iç hata bayraklarıyla gitmem gerekir. Üstesinden gelmek çok zahmetli olacak mı, yoksa istisnalardan daha iyi çalışacak mı? Her iki vakanın karşılaştırılması en iyi cevap olacaktır.
Bazı gömülü sistemlerde veya kullanımlarını yasaklayan belirli bir durumda çalışmadığınız sürece, C ++ 'da istisnalardan kaçınmak benim için son derece üretken görünmektedir (bu durumda, hepsinden kaçınmak için yolunuzdan çıkmanız gerekir) Kütüphane ve dil işlevselliği, aksi takdirde throw
kesinlikle kullanmak gibi nothrow
new
).
Herhangi bir nedenle istisnalardan kaçınmanız gerekiyorsa (örn: C API'sını dışa aktardığınız bir modülün C API sınırları boyunca çalışmak), birçok kişi benimle aynı fikirde olmayabilir, ancak aslında OpenGL gibi genel bir hata işleyicisi / durumu kullanmanızı öneririm glGetError()
. Her iş parçacığı için benzersiz bir hata durumuna sahip olmak için iş parçacığı yerel depolamasını kullanabilirsiniz.
Bunun mantığı benim üretim ortamlarındaki ekipleri görmeye alışkın olmamam, ne yazık ki, hata kodları döndürüldüğünde tüm olası hataları iyice kontrol etmem. Kapsamlı olsaydı, bazı C API'leri hemen hemen her C API çağrısında bir hatayla karşılaşabilir ve kapsamlı kontrol aşağıdaki gibi bir şey gerektirir:
if ((err = ApiCall(...)) != success)
{
// Handle error
}
... bu tür kontroller gerektiren API'yı çağıran neredeyse her kod satırı ile. Yine de bu kadar kapsamlı ekiplerle çalışma şansım olmadı. Genellikle bu tür hataları çoğu zaman yarısı, hatta çoğu zaman göz ardı ederler. İstisnaların bana en büyük cazibesi bu. Bu API'yı kaydırır ve throw
bir hatayla eşit olarak eşit yaparsak , istisna muhtemelen göz ardı edilemez ve benim görüşüme ve deneyime göre, istisnaların üstünlüğü burada yatmaktadır.
Ancak istisnalar kullanılamıyorsa, global, iş parçacığı başına hata durumu en azından, hata kodlarını bana döndürmeyle karşılaştırıldığında büyük bir avantaj (eski bir hatayı daha sonradan biraz daha geç yakalama şansına sahip olabilir) tamamen eksik ve bize ne olduğunu tamamen kayıtsız bırakmak yerine bazı özensiz kod temeli oluştu. Hata birkaç satır önce veya önceki bir işlev çağrısında meydana gelmiş olabilir, ancak yazılım henüz çökmediyse, geriye doğru çalışmaya başlayabilir ve nerede ve neden oluştuğunu anlayabiliriz.
Bana öyle geliyor ki, işaretçiler nadir olduğu için, istisnalardan kaçınmayı seçersem iç hata bayraklarıyla gitmem gerekir.
Mutlaka işaretçilerin nadir olduğunu söylemem. Artık C ++ 11 ve sonrasında, kapların temel veri işaretleyicilerine ve yeni bir nullptr
anahtar kelimeye ulaşmak için yöntemler bile var . İstisnaların varlığında RAII uyumlu olmasının ne kadar kritik olduğu göz önüne alındığında, bunun yerine bir şeyi kullanabiliyorsanız, hafızanın sahibi olmak / yönetmek için ham işaretçiler kullanmanın genellikle akıllıca olmadığı düşünülür unique_ptr
. Ancak hafızanın sahibi olmayan / yönetmeyen ham işaretçiler mutlaka (Sutter ve Stroustrup gibi insanlardan bile) çok kötü olarak kabul edilmez ve bazen bir şeye işaret etme yolu olarak (şeylere işaret eden endekslerle birlikte) çok pratiktir.
Bunlar, geçersiz kılındıktan sonra onlardan vazgeçmeye çalışıp çalışmadığınızı algılamayacak olan standart konteyner yineleyicilerinden (en azından sürümde, kontrol edilen yineleyiciler yok) daha az güvenli değildir. C ++, özel kullanımınız her şeyi sarmak ve sahip olmayan ham işaretçileri bile gizlemek istemediği sürece, hala utanç verici bir şekilde biraz tehlikeli bir dildir. Kaynakların RAII'ye (genellikle çalışma zamanı maliyeti olmadan gelir) uygun olması istisnalarla neredeyse kritiktir, ancak bunun dışında bir geliştiricinin açıkça istemediği maliyetlerden kaçınmak için kullanmak için en güvenli dil olmaya çalışmak gerekmez. başka bir şeyle takas etmek. Önerilen kullanım, sizi sarkan işaretçiler ve geçersiz yineleyiciler gibi şeylerden korumaya çalışmamaktadır, aksi takdirde (aksi takdirde kullanmaya teşvik ediliriz)shared_ptr
Stroustrup'un şiddetle karşı çıktığı her yerde). Bir şey olduğunda bir kaynağı düzgün bir şekilde serbest bırakmama / serbest bırakma / yok etme / kilidini açma / temizleme konusunda sizi korumaya çalışıyor throws
.