Neden inşaatçılar miras almıyor?


33

Bir kurucu temel sınıftan miras kaldığında sorunların neler olabileceği konusunda kafam karıştı. Cpp Astar Plus diyor ki,

Yapıcılar, diğer nesneler yeni nesneler oluşturdukları için diğer sınıf yöntemlerinden farklıdır, oysa diğer yöntemler mevcut nesneler tarafından çağrılır . Bu, yapıcıların miras alınmamasının bir nedenidir . Kalıtım, türetilmiş bir nesnenin temel sınıf yöntemini kullanabileceği anlamına gelir; ancak, yapıcılar söz konusu olduğunda, yapıcı işini yapana kadar nesne mevcut olmaz.

Nesnenin yapımı tamamlanmadan yapıcının çağrıldığını anlıyorum.

Nasıl (çocuk sınıfını miras eğer problemlere yol açabilir Ben çocuk sınıf üst sınıf yöntemini vb sadece üst sınıf yöntemini erişimi yok geçersiz kılmak mümkün olduğu anlamına devralan tarafından ana yapıcısı)?

Nesneleri oluştururken haricinde açıkça bir kurucudan bir kod içinden [henüz farkında olduğumdan değil.] Çağrılma zorunluluğu olmadığını biliyorum. O zaman bile, ebeveyn konstrüktörü çağırmak için bazı mekanizmaları kullanarak bunu yapabilirsiniz [cpp kullanma, kullanma ::veya kullanma member initialiser list, In java kullanma super]. Java'da ilk satırda onu çağırmak için bir zorlama var, bunun ana nesnenin önce yaratıldığından emin olduktan sonra alt nesne inşaatının ilerlemesini sağlamak olduğunu anlıyorum.

Bunu geçersiz kılabilir . Ancak bunun bir sorun teşkil edebileceği durumlarla gelemem. Çocuk ebeveyn kurucuyu miras alırsa ne yanlış gidebilir?

Öyleyse bu sadece gereksiz işlevleri mirastan uzak tutmak için mi? Yoksa dahası var mı?


C ++ 11 ile gerçekten hız kazanmıyor ama bence içinde bir tür kurucu mirası var.
yannis

3
Yapıcılarda ne yaparsanız yapın, yıkıcılarla ilgilenmeniz gerektiğini unutmayın.
Manoj R,

2
Bence "bir kurucu miras" ile ne kastedildiğini tanımlamanız gerekir. Tüm cevapların “bir kurucu devralmanın” ne anlama geldiğini düşündüğü konusunda farklılıklar var gibi görünüyor. Hiçbirinin ilk yorumuma uyduğunu sanmıyorum. "Yukarıdan" gri kutudaki "tanımı, Kalıtım, türetilmiş bir nesnenin temel sınıf yöntemini kullanabileceği anlamına gelir", yapıcıların kalıtımsal olduğu anlamına gelir. Türetilmiş bir nesne kesinlikle her zaman taban sınıfı kurucu yöntemleri kullanabilir ve kullanır.
Dunk

1
Bir yapıcı devralmanın netleşmesi için teşekkürler, şimdi bazı cevaplar alabilirsiniz.
Dunk,

Yanıtlar:


41

Türetilmiş bir sınıfın kurucusunun, bir temel sınıf kurucusunun yapmak zorunda olmadığı ve bilmediği ilave eylemler yapması gerektiğinden, C ++ 'da kurucuların uygun bir kalıtım yolu olamaz. Bu ilave eylemler, türetilmiş sınıfın veri üyelerinin başlatılmasıdır (ve tipik bir uygulamada, vpointer'ı türetilebilir sınıflara atıfta bulunmak üzere ayarlayan).

Bir sınıf inşa edildiğinde, her zaman yapılması gereken birkaç şey vardır: Temel sınıfın yapıcıları (varsa) ve doğrudan üyelerin çağrılması gerekir ve herhangi bir sanal işlev varsa, vpointer'ın doğru ayarlanması gerekir . Sınıfınız için bir kurucu sağlamıyorsanız, derleyici gerekli işlemleri yapan başka bir şey yapacaktır . Eğer varsa yapmak bir kurucu sağlamaktayız, ancak gerekli işlemlerden bazılarını kaçırmak (örneğin, bazı üyelerin başlatılması), sonra derleyici otomatik kurucusuna eksik eylemleri katacak. Bu şekilde, derleyici her sınıfın en az bir kurucuya sahip olmasını ve her kurucunun oluşturduğu nesneleri tamamen başlatmasını sağlar.

C ++ 11'de, derleyiciye, sizin için temel sınıftan kurucularla aynı argümanları alan ve bu argümanları sadece sınıftan ileri süren bir dizi kurucu oluşturma talimatını verebileceğiniz bir 'kurucu devralma' biçimi sunulmuştur. temel sınıf
Resmen miras olarak adlandırılmasına rağmen, gerçekten öyle değil, çünkü hala türetilmiş bir sınıfa özgü işlev var. Şimdi sadece sizin tarafınızdan açıkça yazılmak yerine derleyici tarafından oluşturulur.

Bu özellik şöyle çalışır:

struct Base {
    Base(int a) : i(a) {}
    int i;
};

struct Derived : Base {
    Derived(int a, std::string s) : Base(a), m(s) {}

    using Base::Base; // Inherit Base's constructors.
    // Equivalent to:
    //Derived(int a) : Base(a), m() {}

    std::string m;
};

Derivedşimdi iki yapıcı var (kopyala / taşı yapıcıları saymaz). Bir int alır ve bir dize alır ve bir int alır.


Demek ki bu, çocuk sınıfın kendi kurucusunun olmayacağını varsayar. ve miras kalan kurucu açıkça çocuk üyelerinden ve yapılacak ilk
şeylerden

C ++, bir sınıfın kendi kurucularına sahip olmamasını imkansız kılacak şekilde tanımlanmıştır . Aslında hiç 'yapıcı miras' düşünmüyoruz nedeni budur olmak miras. Bu sıkıcı bir kazan plakası yazmamak için bir yoldur.
Bart van Ingen Schenau

2
Sanırım kafa karışıklığı, boş bir yapıcının bile sahne arkasında çalışmakta olduğu gerçeğinden kaynaklanıyor. Dolayısıyla yapıcı gerçekten iki bölümden oluşur: İç işler ve yazdığınız bölüm. İyi ayrılmadıkları için inşaatçılar miras alınmaz.
Sarien

1
Lütfen nereden m()geldiğini ve türünün mümkünse nasıl değişeceğini belirtmeyi düşünün int.
Deduplicator

Öyle yapmak mümkün using Base::Basemü? Bu çizgiyi türetilmiş bir sınıfta unutursam ve yapıcıyı tüm türetilmiş sınıflarda miras almam gerekiyorsa
Post Self

7

"Ana kurucuyu devralarak" derken ne demek istediğinizi açık değil. Geçersiz kılma kelimesini kullanırsınız; bu, polimorfik sanal fonksiyonlar gibi davranan yapıcılar hakkında düşünmeniz gerektiğini gösterir . Kasıtlı olarak "sanal kurucular" terimini kullanmıyorum, çünkü bu aslında başka bir nesne oluşturmak için zaten var olan bir nesnenin örneğini gerektiren bir kod deseni için kullanılan bir addır .

"Sanal kurucu" modelinin dışındaki polimorfik kuruculara çok az fayda vardır ve gerçek bir polimorfik kurucunun kullanılabileceği somut bir senaryo ile karşılaşmak zordur. Hiçbir şekilde uzaktan bile geçerli olmayan bir C ++ örneği :

struct Base {
  virtual Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) override {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Base(p1, p2); // This might call Derived(unsigned, unsigned).
  Derived *p_d2 = nullptr;
  p_d2 = new Base(p1, p2); // This might call Derived(unsigned, unsigned) too.
}

Bu durumda çağrılan kurucu, yapılandırılan veya atanan değişkenin somut türüne bağlıdır. Ayrıştırma / kod oluşturma sırasında algılanması karmaşıktır ve faydası yoktur: İnşa ettiğiniz beton türünü bilirsiniz ve türetilmiş sınıf için belirli bir kurucu yazmışsınızdır. Aşağıdaki geçerli C ++ kodu tamamen aynıdır, biraz daha kısadır ve yaptığı şeyde daha açıktır:

struct Base {
  Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Derived(p1, p2); 
  Derived *p_d2 = nullptr;
  p_d2 = new Derived(p1, p2);
}


İkinci bir yorum ya da belki ek bir soru - ya Baz sınıfı kurucuları açıkça gizlenmediği sürece tüm türetilmiş sınıflarda otomatik olarak mevcutsa?

Çocuk ebeveyn kurucuyu miras alırsa ne yanlış gidebilir?

Türetilmiş sınıfı oluştururken kullanmakta yanlış olan bir ana kurucuyu gizlemek için ek kod yazmanız gerekir. Bu, türetilmiş sınıf, temel sınıfı belirli parametrelerin alakasız hale geleceği şekilde uzmanlaştığında olabilir.

Tipik örnek dikdörtgenler ve karelerdir (Kareler ve dikdörtgenlerin genellikle Liskov ikame edilemez olmadığına dikkat edin, bu yüzden çok iyi bir tasarım değil, ancak konuyu vurgulamaktadır).

struct Rectangle {
  Rectangle(unsigned width, unsigned height) {...}
};

struct Square : public Rectangle {
  explicit Square(unsigned side) : Rectangle(side, side) {...}
};

Square, iki değerli Rectangle kurucusunu miras aldıysa, farklı yükseklik ve genişlikte kareler inşa edebilirsiniz ... Bu mantıksal olarak yanlış, bu yüzden yapıcıyı gizlemek istersiniz.


3

Yapıcılar neden kalıtsal değildir: cevap şaşırtıcı derecede basittir: Temel sınıfın kurucusu, temel sınıfı “oluşturur” ve kalıtsal sınıfın yapıcısı, kalıtsal sınıfı “oluşturur”. Miras alınan sınıf, yapıcıyı devralacaksa, yapıcı, base base türünde bir nesne oluşturmaya çalışır ve kalıtsal sınıftaki bir nesneyi "oluşturamazsınız".

Hangi tür bir sınıfı devralma amacını yitirir.


3

Türetilmiş sınıfın temel sınıf kurucusunu geçersiz kılmasına izin vermedeki en belirgin sorun, türetilmiş sınıfın geliştiricisinin temel sınıf (ları) nasıl inşa edeceğinin bilinmesinden sorumlu olmasıdır. Türetilmiş sınıf temel sınıfı düzgün bir şekilde oluşturmadığında ne olur?

Ayrıca, Liskov-Sübstitüsyon ilkesi, artık birbirleriyle uyumlu temel sınıf nesneler koleksiyonunuza güvenemeyeceğiniz için geçerli olmayacaktır, çünkü baz sınıfın diğer türetilmiş tiplerle düzgün veya uyumlu bir şekilde inşa edildiğine dair hiçbir garanti yoktur.

1'den fazla kalıtım seviyesi eklendiğinde daha da karmaşıklaşır. Şimdi türetilmiş sınıfınızın tüm temel sınıfları zinciri nasıl kuracağınızı bilmesi gerekiyor.

Peki miras hiyerarşisinin en üstüne yeni bir temel sınıf eklerseniz ne olur? Türetilmiş sınıf yapıcılarının tümünü güncellemeniz gerekir.


2

Yapıcılar temelde diğer yöntemlerden farklıdır:

  1. Yazmazsanız üretilirler.
  2. Manuel olarak yapmasanız bile, tüm temel sınıf yapıcıları örtük olarak adlandırılır.
  3. Onları açıkça değil, nesneler yaratarak çağırıyorsunuz.

Peki neden miras almıyorlar? Basit cevap: Çünkü elle yazılmış veya yazılmış, her zaman bir geçersiz kılma vardır.

Neden her sınıfın bir kurucuya ihtiyacı var? Bu karmaşık bir soru ve cevabın derleyiciye bağlı olduğuna inanıyorum. Derleyicinin buna çağrılmasını zorunlu kılmadığı "önemsiz" bir kurucu gibi bir şey var. Sanırım kalıtım derken neyi kastettiğinize en yakın şey olduğunu düşünüyorum ama yukarıda belirtilen üç nedenden dolayı yapıcıları normal yöntemlerle karşılaştırmanın gerçekten yararlı olmadığını düşünüyorum. :)


1

Her sınıfın bir kurucuya, hatta varsayılanlara bile ihtiyacı vardır.
Özel bir kurucu oluşturmamanız dışında, C ++ sizin için varsayılan kurucular yaratacaktır.
Temel sınıfınızın özel bir kurucu kullanması durumunda, aynı olanı ve zincirlerini geri bağlasa bile, özel kurucuyu türetilmiş sınıfa yazmanız gerekir.
C ++ 11 kullanmakta yapıcıları kod tekrarından kaçınmak için izin kullanarak :

Class A : public B {
using B:B;
....
  1. Bir kalıtımsal kurucu kümesinden oluşur.

    • Temel sınıfın şablon dışı tüm kurucuları (eğer varsa elips parametrelerini çıkardıktan sonra) (C ++ 14'ten beri)
    • Varsayılan argümanlara veya ellipsis parametresine sahip her yapıcı için, elipsleri düşürmek ve argüman listelerinin sonlarından varsayılan argümanları tek tek atlamak suretiyle oluşturulan tüm yapıcı imzaları
    • Temel sınıfın tüm kurucu şablonları (eğer varsa elips parametrelerini çıkardıktan sonra) (C ++ 14'ten beri)
    • Varsayılan argümanlara veya elipslere sahip her yapıcı şablon için, elipsleri düşürmek ve argüman listelerinin sonlarından varsayılan argümanları tek tek atlamak suretiyle oluşturulan tüm yapıcı imzaları
  2. Varsayılan yapıcı ya da kopyala / taşı yapıcısı olmayan ve imzaları türetilmiş sınıftaki kullanıcı tanımlı yapıcılarla eşleşmeyen miras kalan tüm yapıcılar türetilmiş sınıfta örtük olarak bildirilir. Varsayılan parametreler miras alınmaz


0

Kullanabilirsiniz:

MyClass() : Base()

Bunu neden yapmak zorunda olduğunu soruyor musun?

Alt sınıf, kurucuda başlatılması gerekebilecek ek özelliklere sahip olabilir veya temel sınıf değişkenlerini farklı bir şekilde başlatabilir.

Alt tip nesnesini başka nasıl yaratırsınız?


Teşekkürler. Aslında, diğer ebeveyn sınıf yöntemleri gibi, neden bir sınıfın miras almadığını kurucu olarak anlamaya çalışıyorum.
Suvarna Pattayil
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.