C ++ sınıfı yapıcısındaki başarısızlık durumları nasıl ele alınır?


21

Yapıcısı bazı işlemler yapan bir CPP sınıfım var. Bu işlemlerin bazıları başarısız olabilir. Yapıcıların hiçbir şey getirmediğini biliyorum.

Benim sorularım

  1. Bir kurucuda üyeleri başlatan başka işlemler yapmasına izin verilir mi?

  2. Çağıran fonksiyona, yapıcıdaki bazı işlemlerin başarısız olduğunu söylemek mümkün müdür?

  3. new ClassName()Yapıcıda bazı hatalar oluşursa NULL dönüş yapabilir miyim ?


22
Yapıcı içinden bir istisna atabilirsiniz. Bu tamamen geçerli bir kalıp.
Andy,

1
Muhtemelen GoF'un yaratıcı yapılarına bir göz atmanız gerekir . Fabrika modelini tavsiye ederim.
SpaceTrucker

2
Ortak bir # 1 örneği veri doğrulamadır. IE eğer bir sınıfınız varsa Square, bir parametreyi alan bir yapıcı ile, bir tarafın uzunluğunu, bu değerin 0'dan büyük olup olmadığını kontrol etmek istersiniz.
David, Reinstate Monica

1
İlk soru için, sizi sanal işlevlerin yapıcılarda istenmeyen bir şekilde davranabileceği konusunda uyarmama izin verin. Yapısızlaştırıcılar ile aynı. Böyle çağırırken sakının.

1
# 3 - NULL olarak geri dönmek istiyorsun? OO'nun faydalarından biri, geri dönüş değerlerini kontrol etmek DEĞİLDİR. Sadece uygun potansiyel istisnaları yakala ().
MrWonderful

Yanıtlar:


42
  1. Evet, bazı kodlama standartları bunu yasaklayabilir olsa da.

  2. Evet. Önerilen bir istisna atmaktır. Alternatif olarak, hata bilgilerini nesnenin içinde saklayabilir ve bu bilgilere erişmek için yöntemler sağlayabilirsiniz.

  3. Yok hayır.


4
Oluşturucu argümanlarının bir kısmı gereksinimleri karşılamadığı ve bu nedenle bir hata olarak işaretlendiği halde nesne hala geçerli durumda değilse, 2) gerçekten yapılması tavsiye edilmez. Bir nesnenin ya geçerli bir durumda olması ya da hiç olmaması daha iyidir.
Andy,

@DavidPacker Anlaşıldı, buraya bakın: stackoverflow.com/questions/77639/… Ancak bazı kodlama kuralları, inşaatçılar için sorunlu olan istisnaları yasaklar.
Sebastian Redl

Her nasılsa, bu cevap için sana zaten bir ısrar verdim Sebastian. İlginç. : D
Andy,

10
@ooxi Hayır, değil. Geçersiz kılınan yeni bellek ayırmak için çağrılır, ancak yapıcıya yapılan çağrı operatörünüz döndükten sonra derleyici tarafından yapılır; bu, hatayı yakalayamayacağınız anlamına gelir. Buna yeni denildiği varsayılıyor; yığın-tahsisli nesneler için değildir, bunların çoğu olmalıdır.
Sebastian Redl,

1
# 1 için RAII, yapıcıda daha fazlasını yapmanız gerekebileceği yaygın bir örnektir.
Eric,

20

Hesaplamayı gerçekleştiren ve başarı durumunda bir nesneyi döndüren veya başarısızlık durumunda olmayan bir statik yöntem oluşturabilirsiniz.

Nesnenin bu konstrüksiyonunun nasıl yapıldığına bağlı olarak, statik olmayan bir yöntemde nesnelerin oluşturulmasına izin veren başka bir nesne yaratmak daha iyi olabilir.

Bir kurucuya dolaylı olarak çağırmak çoğu zaman "fabrika" olarak adlandırılır.

Bu ayrıca boş bir nesneyi döndürmenize izin verir, bu da boş değer döndürmekten daha iyi bir çözüm olabilir.


@ Teşekkürler! Maalesef burada iki cevabı kabul edemem :( Aksi takdirde bu cevabı da kabul ederdim! Tekrar teşekkürler!
MayurK

Hiçbir endişe @MayurK, kabul cevabı işaretlemek için değil doğru cevap, ama senin için çalışmış biri.
null

3
@null: C ++ 'da, yalnızca geri dönemezsiniz NULL. Örneğin, int foo() { return NULLaslında 0bir tamsayı nesnesi olan (sıfır) döndürürsünüz . In std::string foo() { return NULL; }yanlışlıkla arayacağını std::string::string((const char*)NULL)(NULL bir \ 0 sonlandırılmış dize işaret etmez) Tanımsız Davranış olan.
MSalters,

3
std :: isteğe bağlı bir yol olabilir ancak bu şekilde gitmek istiyorsanız her zaman boost :: isteğe bağlı kullanabilirsiniz.
Sean Burton,

1
@Vld: C ++ 'da, nesneler sınıf türleriyle sınırlı değildir. Ve genel programlama ile, fabrikaları ile bitmesi nadir değildir int. Örneğin std::allocator<int>mükemmel aklı başında bir fabrika.
MSalters,

5

@SebastianRedl zaten basit, doğrudan cevapları verdi, ancak bazı ek açıklamalar faydalı olabilir.

TL; DR = yapıcıları basit tutmak için bir stil kuralı var, bunun sebepleri var, ama bu nedenler çoğunlukla tarihi (veya sadece kötü) bir kodlama stiliyle ilgilidir. Yapıcılarda istisnaların işlenmesi iyi tanımlanmıştır ve yıkıcılar hala tamamen inşa edilmiş yerel değişkenler ve üyeler için çağrılacaktır, yani deyimsel C ++ kodunda herhangi bir sorun olmamalıdır. Stil kuralı yine de devam eder, ancak normalde bu bir sorun değildir - tüm başlatmalar kurucuda olmak zorunda değildir ve özellikle bu kurucu olmak zorunda değildir.


Bu , yapıcıların tanımlanmış bir geçerli durum oluşturmak için ellerinden gelenin en iyisini yapmaları gereken yaygın bir stil kuralıdır . İlklendirmeniz daha karmaşıksa, yapıcının dışında kullanılmalıdır. Yapıcınızın ayarlayabileceği ucuz bir başlangıç ​​değeri yoksa, bir tane eklemek için sınıfınızın zorladığı işgalcileri zayıflatmalısınız. Örneğin, sınıfınızın yönetmesi için depolama alanı ayırmak çok pahalıysa, elbette null gibi özel durum durumlarının hiç kimsenin soruna neden olmadığından tahsis edilmemiş ancak boş bir durum ekleyin. Ahem.

Yaygın olmasına rağmen, kesinlikle bu aşırı biçimde mutlak olmaktan çok uzak. Özellikle, alayımın da belirttiği gibi, işgalcileri zayıflatmanın neredeyse her zaman çok yüksek bir fiyat olduğunu söyleyen kamptayım. Ancak, stil kuralı arkasında nedenler vardır, ve minimal kurucular ikisine de sahip yolları var ve güçlü değişmezleri.

Sebepler, özellikle istisnalar karşısında otomatik yıkıcı temizliği ile ilgilidir. Temel olarak, derleyici yıkıcıları çağırmaktan sorumlu olduğunda iyi tanımlanmış bir nokta olmalıdır. Hala bir yapıcı çağrısındayken, nesne mutlaka tam olarak oluşturulmuş değildir, bu nedenle bu nesneye yönelik yıkıcıyı çağırmak geçerli değildir. Bu nedenle, nesneyi imha etme sorumluluğu yalnızca yapıcı başarıyla tamamlandığında derleyiciye geçer. Bu, gerçekten en iyi isim olmayan RAII (Kaynak Tahsisi Başlatılıyor) olarak bilinir.

Yapıcı içinde bir istisna atması meydana gelirse, kısmen yapılmış herhangi bir şeyin, tipik olarak a try .. catch.

Ancak, başarılı bir şekilde inşa edilmiş olan nesnenin bileşenleri zaten derleyicilerin sorumluluğundadır. Bu, pratikte bunun gerçekten önemli bir şey olmadığı anlamına gelir. Örneğin

classname (args) : base1 (args), member2 (args), member3 (args)
{
}

Bu yapıcının gövdesi boş. Yani uzun süre kurucular olarak base1, member2ve member3istisna güvenli, endişelenecek bir şey yok. Örneğin, eğer bir kurucu member2fırlatırsa, bu kurucu kendini temizlemekten sorumludur. Taban base1zaten tamamen inşa edildi, bu yüzden yıkıcısı otomatik olarak çağrılacak. member3hiç bir zaman bile kısmen inşa edilmedi, bu yüzden de temizlenmesi gerekmiyor.

Bir ceset varken bile, istisna atılmadan önce tamamen oluşturulan yerel değişkenler, diğer işlevler gibi otomatik olarak imha edilir. Ham işaretçilerle hokkabazlık yapan veya "kendi" bir tür örtülü durumun (başka yerde saklanan) "tipik" bir başlatma / alma işlevi çağrısının bir son / bırakma çağrısıyla eşleştirilmesi gerektiği anlamına gelen yapıcı organlar istisna güvenlik sorunlarına neden olabilir bir kaynağı bir sınıf aracılığıyla doğru şekilde yönetemiyor. Örneğin, ham işaretçileri unique_ptrkurucu ile değiştirirseniz , gerekirse yıkıcı unique_ptrotomatik olarak çağrılır.

İnsanların en az yapıcıları tercih etmeleri için verdikleri başka nedenler var. Birincisi, stil kuralının var olmasından dolayı, çoğu insan yapıcı çağrılarının ucuz olduğunu varsayar. Buna rağmen hala güçlü değişmezlere sahip olmanın bir yolu, bunun yerine zayıflamış değişmezlere sahip olan ve (potansiyel olarak birçok) normal üye işlev çağrıları kullanarak gerekli başlangıç ​​değerini ayarlayan ayrı bir fabrika / üretici sınıfına sahip olmaktır. İhtiyacınız olan başlangıç ​​durumuna sahip olduğunuzda, bu nesneyi güçlü değişmezlerin bulunduğu sınıfın yapıcısına argüman olarak iletin. Bu zayıf değişmez nesnenin "bağırsaklarını çalabilir" - anlamsallığı taşıyor - ki bu ucuz (ve genellikle noexcept) bir işlemdir.

Ve elbette bunu bir make_whatever ()fonksiyona sarabilirsiniz , böylece bu işlevin arayanları asla zayıflamış değişmez sınıf örneğini görmek zorunda kalmazlar.


Yazdığınız paragraf "Hala bir yapıcı çağrısındayken, nesne mutlaka tam olarak oluşturulmamıştır, bu nedenle bu nesne için yıkıcıyı çağırmak geçerli değildir. Bu nedenle, nesneyi yok etme sorumluluğu yalnızca derleyiciye aktarılır. Yapıcı başarıyla tamamlandığında, "temsilcilerin dağıtılmasıyla ilgili bir güncelleştirme gerçekten kullanabilir. Herhangi bir en-türevi yapıcı bittiğinde amacı tam olarak inşa edilir, ve yıkıcı olacak bir istisna delegating kurucu içinde meydana geldiğinde çağrılacak.
Ben Voigt,

Bu nedenle, "minimum" yapıcısı özel olabilir ve "make_whatever ()" işlevi, özel olanı çağıran başka bir yapıcı olabilir.
Ben Voigt,

Bu, aşina olduğum RAII'nin tanımı değil. Benim RAII anlayışım kasıtlı olarak bir nesnenin kurucusunda bir kaynak elde etmek (ve sadece içinde) ve onu yok edici içinde bırakmaktır. Bu şekilde nesne, kapsüllenen kaynakların edinilmesini ve serbest bırakılmasını otomatik olarak yönetmek için istif üzerinde kullanılabilir. Klasik örnek, inşa edildiğinde bir muteks elde eden ve imha edildiğinde serbest bırakan bir kilit.
Eric,

1
@Eric - Evet, kesinlikle standart bir uygulamadır - genellikle RAII olarak adlandırılan standart bir uygulamadır. Tanımı genişleten sadece benim değil, hatta bazı konuşmalarda Stroustrup. Evet, RAII kaynak yaşam döngülerini nesne yaşam döngülerine bağlamakla ilgilidir, zihinsel model sahipliktir.
Steve314,

1
@Eric - önceki yanıtlar silindi çünkü çok kötü bir şekilde açıklanmışlardı. Her neyse, nesnelerin kendileri sahiplenilebilecek kaynaklardır. Her şey bir fonksiyona ya da statik / global değişkenlere kadar zincir halinde sahip olmalıdırmain . Kullanılarak ayrılan bir nesne new, değil o sorumluluk atamak kadar sahip olunan, ancak akıllı işaretçiler referans verdikleri yığın-tahsis nesneleri kendi ve konteynerler kendi veri yapılarını kendi. Sahipler erken silmeyi seçebilir, sahipleri imha edici sonuçta sorumludur.
Steve314
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.