İçeriğe geç
academia.sh

Ders 11 / 14

Çoklu Kalıtım ve Elmas Sorunu

Çoklu kalıtımda ikinci taban alt nesnenin türetilmiş nesneyle aynı adresi paylaşmadığının ve elmas biçimli kalıtımda ortak atanın sanal kalıtım olmadan ikiye katlandığının ölçümü.

Yayın Güncelleme

Bu derste

  • Çoklu kalıtımda yalnız ilk taban alt nesnenin türetilmiş nesneyle aynı adresi paylaştığını ölçer
  • Elmas biçimli kalıtımda ortak bir atanın sanal kalıtım olmadan iki ayrı kopyaya bölündüğünü gösterir
  • Sanal kalıtımın ortak atayı tek bir paylaşılan taban alt nesneye indirdiğini doğrular
İçindekiler

Önceki iki ders tekli kalıtımı ölçtü: bir türetilmiş nesnenin tek bir taban alt nesnesi vardı ve o alt nesnenin adresi hep türetilmiş nesnenin adresiyle çakıştı. Bu ders bir sınıfın birden çok taban sınıftan aynı anda türediği durumu ele alıyor. İlk taban alt nesne yine türetilmiş nesnenin başında duruyor mu? İkincisi de aynı adresi mi paylaşıyor? Ve iki taban sınıfın ortak bir atası varsa, o ata türetilmiş nesnenin içinde kaç kez yer kaplıyor?

Çoklu Kalıtımda İkinci Taban Alt Nesnenin Adresi Farklı

Triatletci, birbiriyle ilişkisiz iki sınıftan türüyor: Yuzucu ve Kosucu. İlk taban alt nesne türetilmiş nesnenin başında mı duruyor, ikincisi de aynı adresi mi paylaşıyor?

// coklu_kalitim_ofset.cpp — coklu kalitimda ilk taban ayni adreste, ikincisi degil
#include <iostream>

class Yuzucu {
public:
    int stil_ = 1;
};

class Kosucu {
public:
    int mesafe_ = 5;
};

class Triatletci : public Yuzucu, public Kosucu {
public:
    int toplam_puan_ = 0;
};

int main() {
    Triatletci t;

    std::cout << std::boolalpha;
    std::cout << "t ile ilk taban Yuzucu adresinin ayni olup olmadigi "
              << (static_cast<void*>(&t) == static_cast<void*>(static_cast<Yuzucu*>(&t))) << "\n";
    std::cout << "t ile ikinci taban Kosucu adresinin ayni olup olmadigi "
              << (static_cast<void*>(&t) == static_cast<void*>(static_cast<Kosucu*>(&t))) << "\n";

    return 0;
}
t ile ilk taban Yuzucu adresinin ayni olup olmadigi true
t ile ikinci taban Kosucu adresinin ayni olup olmadigi false

CB10. Triatletci, kalıtım listesinde önce Yuzucu’yu, sonra Kosucu’yu bildiriyor — derleyici taban alt nesneleri bu sırayla yerleştiriyor. İlk taban alt nesne (Yuzucu) t’nin başında duruyor, bu yüzden &t ile static_cast<Yuzucu*>(&t) aynı adresi taşıyor, true. İkinci taban alt nesne (Kosucu) ise Yuzucu alt nesnesinden sonraki bir ofsette duruyor; &t ile static_cast<Kosucu*>(&t) farklı adresler, false. Tekli kalıtımda (Kalıtım dersi) taban alt nesnenin adresi hep türetilmiş nesneyle çakışıyordu çünkü taşınacak tek bir taban vardı; çoklu kalıtımda bu yalnız ilk taban için geçerli. Sınıf parça: her iki taban alt nesne de t’nin bir parçası, ama aynı adresi paylaşan yalnız ilki.

Elmas Sorunu: Ortak Atadan İki Ayrı Kopya

Yuzucu ve Kosucu’nun ortak bir atası Canli olsun. Triatletci ikisinden birden türediğinde, Canli alt nesnesine Yuzucu üzerinden ve Kosucu üzerinden ulaşmak aynı nesneye mi çıkıyor, yoksa iki ayrı kopyaya mı?

// elmas_ayrik.cpp — sanal kalitim olmadan ortak ata iki ayri kopyaya bolunuyor
#include <iostream>

class Canli {
public:
    int can_ = 100;
};

class Yuzucu : public Canli {};
class Kosucu : public Canli {};
class Triatletci : public Yuzucu, public Kosucu {};

int main() {
    Triatletci t;

    Canli* yuzucu_uzerinden = static_cast<Canli*>(static_cast<Yuzucu*>(&t));
    Canli* kosucu_uzerinden = static_cast<Canli*>(static_cast<Kosucu*>(&t));

    std::cout << std::boolalpha;
    std::cout << "Yuzucu uzerinden ve Kosucu uzerinden ulasilan Canli'nin ayni olup olmadigi "
              << (static_cast<void*>(yuzucu_uzerinden) == static_cast<void*>(kosucu_uzerinden)) << "\n";
    std::cout << "Triatletci boyutunun iki ayri Canli'yi barindiracak kadar buyuk olup olmadigi "
              << (sizeof(Triatletci) >= 2 * sizeof(Canli)) << "\n";

    return 0;
}
Yuzucu uzerinden ve Kosucu uzerinden ulasilan Canli'nin ayni olup olmadigi false
Triatletci boyutunun iki ayri Canli'yi barindiracak kadar buyuk olup olmadigi true

CB11. Yuzucu ve Kosucu, Canli’den sıradan (sanal olmayan) kalıtımla türüyor; her biri kendi Canli alt nesnesini taşıyor. Triatletci her ikisinden türediğinde, bu iki Canli kopyası da Triatletci’nin içine giriyor — birleşmiyor. yuzucu_uzerinden ile kosucu_uzerinden farklı adresler taşıyor, false: bunlar iki ayrı Canli nesnesi. sizeof(Triatletci)’nin en az iki Canli büyüklüğünü kapsaması bunu bellek düzeninde doğruluyor, true. Bu, elmas sorunu: t.can_ yazmak hangi kopyanın kastedildiği belirsiz olduğu için derleyiciyi durdurur, yalnız t.Yuzucu::can_ ya da t.Kosucu::can_ gibi açık bir yol belirtilirse derlenir — bu ders o belirsiz satırı hiç çalıştırmıyor, yalnız iki kopyanın varlığını adres ve boyut ölçümüyle gösteriyor. Sınıf parça: Triatletci, Canli’nin bir değil, iki ayrı parçasını taşıyor.

Sanal Kalıtım: Tek Paylaşılan Taban Alt Nesnesi

Yuzucu ve Kosucu, Canli’den sanal kalıtımla türerse, Triatletci her ikisinden türediğinde Canli alt nesnesi yine ikiye mi bölünüyor, yoksa tek bir paylaşılan nesneye mi indiriliyor?

// elmas_sanal.cpp — sanal kalitim ortak atayi tek paylasilan alt nesneye indiriyor
#include <iostream>

class Canli {
public:
    int can_ = 100;
};

class Yuzucu : virtual public Canli {};
class Kosucu : virtual public Canli {};
class Triatletci : public Yuzucu, public Kosucu {};

int main() {
    Triatletci t;

    Canli* yuzucu_uzerinden = static_cast<Canli*>(static_cast<Yuzucu*>(&t));
    Canli* kosucu_uzerinden = static_cast<Canli*>(static_cast<Kosucu*>(&t));

    std::cout << std::boolalpha;
    std::cout << "Yuzucu uzerinden ve Kosucu uzerinden ulasilan Canli'nin ayni olup olmadigi "
              << (static_cast<void*>(yuzucu_uzerinden) == static_cast<void*>(kosucu_uzerinden)) << "\n";
    std::cout << "t.can_ ile yuzucu_uzerinden->can_ 'in ayni adresi tasiyip tasimadigi "
              << (static_cast<void*>(&t.can_) == static_cast<void*>(&yuzucu_uzerinden->can_)) << "\n";

    return 0;
}
Yuzucu uzerinden ve Kosucu uzerinden ulasilan Canli'nin ayni olup olmadigi true
t.can_ ile yuzucu_uzerinden->can_ 'in ayni adresi tasiyip tasimadigi true

CB12. virtual public Canli bildirimi, Yuzucu ve Kosucu’ya “kendi Canli kopyanı kurma, en tepedeki türetilmiş sınıfın paylaştığı tek kopyayı kullan” diyor. Triatletci her ikisinden türediğinde artık tek bir Canli alt nesnesi var — yuzucu_uzerinden ile kosucu_uzerinden aynı adresi taşıyor, true. Bu paylaşım sayesinde t.can_ de artık belirsiz değil, doğrudan derleniyor ve aynı paylaşılan nesneye işaret ediyor, true. Önceki ölçümdeki elmas sorunu — iki ayrı Canli kopyası — burada ortadan kalktı, çünkü Canli’ye giden iki yol da aynı tek nesnede birleşiyor. Sınıf parça: Triatletci, Canli’nin artık iki değil, tek bir parçasını taşıyor ve bu parçaya hem Yuzucu hem Kosucu üzerinden ulaşılıyor.

Nitelenmiş Adla İki Ayrı Kopyaya Erişip Yalnız Birini Değiştirmek

CB11, t.can_ yazmanın hangi kopya kastedildiği belirsiz olduğu için derlenmediğini, yalnız t.Yuzucu::can_ ya da t.Kosucu::can_ gibi nitelenmiş bir adla derlendiğini söylemişti ama bu satırı hiç çalıştırmamıştı. Nitelenmiş adla yalnız bir kopya değiştirildiğinde, diğer kopya gerçekten etkilenmeden mi kalıyor?

// coklu_kalitim_nitelenmis_erisim.cpp — sanal olmayan kalitimda iki ayri Canli kopyasina nitelenmis adla erisilip biri degistiriliyor
#include <iostream>

class Canli {
public:
    int can_ = 100;
};

class Yuzucu : public Canli {};
class Kosucu : public Canli {};
class Triatletci : public Yuzucu, public Kosucu {};

int main() {
    Triatletci t;

    t.Yuzucu::can_ = 70;

    std::cout << "t.Yuzucu::can_ degistirildikten sonra degeri " << t.Yuzucu::can_ << "\n";
    std::cout << "t.Kosucu::can_ degismeden kalan degeri " << t.Kosucu::can_ << "\n";
    std::cout << std::boolalpha
              << "t.Yuzucu::can_ ile t.Kosucu::can_ 'in ayni adresi tasiyip tasimadigi "
              << (static_cast<void*>(&t.Yuzucu::can_) == static_cast<void*>(&t.Kosucu::can_)) << "\n";

    return 0;
}
t.Yuzucu::can_ degistirildikten sonra degeri 70
t.Kosucu::can_ degismeden kalan degeri 100
t.Yuzucu::can_ ile t.Kosucu::can_ 'in ayni adresi tasiyip tasimadigi false

CB32. t.Yuzucu::can_ = 70; nitelenmiş adı kullanarak derleyiciye hangi kopyanın kastedildiğini açıkça söylüyor ve yalnız o kopyayı değiştiriyor. t.Kosucu::can_ sorgulandığında hâlâ ilk değeri, 100, dönüyor — Yuzucu üzerinden yapılan değişiklik Kosucu‘nun kendi Canli kopyasına hiç sızmıyor. Adres karşılaştırması bunu doğruluyor: false, iki nitelenmiş erişim gerçekten iki ayrı nesneye ulaşıyor. Bu, CB11’in bellek boyutuyla dolaylı gösterdiği şeyi doğrudan kanıtlıyor: Triatletci’nin içinde Canli bir kez değil, iki kez var ve nitelenmiş erişim programcıya bu iki kopyadan hangisiyle çalışacağını seçme imkânı veriyor — derleyicinin kendiliğinden seçemediği bir kararı elle vermek. Sınıf parça: t.Yuzucu::can_ ve t.Kosucu::can_, t’nin iki farklı parçası, bağımsız olarak değişebiliyorlar.

Sanal Tabanın Yapıcısını En Türemiş Sınıf Çağırıyor

Canli’nin artık parametre alan, varsayılan olmayan bir kurucusu var. Yuzucu ve Kosucu kendi kurucularında Canli’yi kendi değerleriyle kurmaya çalışıyor, ama Triatletci de kendi kurucusunda Canli’yi ayrı bir değerle kuruyor. Sanal kalıtımda paylaşılan can_ hangi kurucunun verdiği değeri taşıyor?

// elmas_sanal_yapici_zinciri.cpp — sanal tabanin yapicisini en turemis sinif dogrudan cagiriyor, aradakiler gorunmezden geliniyor
#include <iostream>

class Canli {
public:
    explicit Canli(int can) : can_(can) {
        std::cout << "Canli yapicisi can=" << can_ << "\n";
    }
    int can_;
};

class Yuzucu : virtual public Canli {
public:
    Yuzucu() : Canli(1) {
        std::cout << "Yuzucu yapicisi (kendi Canli(1) cagrisi gorunmezden gelinecek)\n";
    }
};

class Kosucu : virtual public Canli {
public:
    Kosucu() : Canli(2) {
        std::cout << "Kosucu yapicisi (kendi Canli(2) cagrisi gorunmezden gelinecek)\n";
    }
};

class Triatletci : public Yuzucu, public Kosucu {
public:
    Triatletci() : Canli(100), Yuzucu(), Kosucu() {
        std::cout << "Triatletci yapicisi, paylasilan can_ = " << can_ << "\n";
    }
};

int main() {
    Triatletci t;
    return 0;
}
Canli yapicisi can=100
Yuzucu yapicisi (kendi Canli(1) cagrisi gorunmezden gelinecek)
Kosucu yapicisi (kendi Canli(2) cagrisi gorunmezden gelinecek)
Triatletci yapicisi, paylasilan can_ = 100

CB33. Canli yapicisi can=100 yalnız bir kez basılıyor, can=1 ya da can=2 hiç görünmüyor — sanal tabanın kurucusunu çağırma sorumluluğu, ara sınıflarda değil, en türemiş sınıfta. Yuzucu’nun ve Kosucu’nun kendi mem-initializer listelerindeki Canli(1) ve Canli(2) çağrıları, Triatletci üzerinden kurulurken gözardı ediliyor; bu satırlar yalnız Yuzucu ya da Kosucu tek başına (Triatletci’siz) kurulduğunda devreye girerdi. Triatletci‘nin kendi mem-initializer listesindeki Canli(100) ise gerçekten çalışan tek çağrı — bu yüzden paylaşılan can_ 100. Sanal tabanlar, kalıtım zincirindeki konumlarından bağımsız olarak, her zaman en tepedeki türetilmiş sınıfın kurucusu tarafından ilklendiriliyor; bu da CB12’nin ölçtüğü “tek paylaşılan nesne” ilkesinin kurucu tarafındaki karşılığı — paylaşılan nesne tek olduğu için, onu kuracak çağrı da tek olmak zorunda. Sınıf parça: can_, t’nin tek bir paylaşılan parçası ve bu parçanın değerini yalnız en türemiş sınıfın kurucusu belirliyor.

Sanal Kalıtımın Yapısal Maliyeti: Kopya Azalıyor, İşaretçi Ekleniyor

CB11 sanal olmayan elmasta Canli’nin iki kez yer kapladığını, CB12 sanal elmasta tek kopyaya indiğini göstermişti. Kopya sayısı azalırken toplam nesne boyutu da aynı oranda mı küçülüyor, yoksa sanal kalıtımın kendi ek yükü bu kazancın bir kısmını mı yiyor?

// elmas_sanal_yapisal_maliyet.cpp — sanal kalitimin nesne boyutuna etkisi ayni sinif kumesinin sanalsiz halidir karsilastiriliyor
#include <iostream>

class Canli {
public:
    int can_ = 1;
};

class YuzucuDuz : public Canli {};
class KosucuDuz : public Canli {};
class TriatletciDuz : public YuzucuDuz, public KosucuDuz {};

class YuzucuSanal : virtual public Canli {};
class KosucuSanal : virtual public Canli {};
class TriatletciSanal : public YuzucuSanal, public KosucuSanal {};

int main() {
    std::cout << std::boolalpha
              << "sanal elmasin boyutu sanalsiz elmastan kucuk mu: "
              << (sizeof(TriatletciSanal) < sizeof(TriatletciDuz)) << "\n";
    std::cout << "sanal elmasin boyutu tek bir Canli'den buyuk mu (ek yuk var mi): "
              << (sizeof(TriatletciSanal) > sizeof(Canli)) << "\n";
    std::cout << "sanalsiz elmasin boyutu en az iki Canli kadar mi: "
              << (sizeof(TriatletciDuz) >= 2 * sizeof(Canli)) << "\n";

    return 0;
}
sanal elmasin boyutu sanalsiz elmastan kucuk mu: false
sanal elmasin boyutu tek bir Canli'den buyuk mu (ek yuk var mi): true
sanalsiz elmasin boyutu en az iki Canli kadar mi: true

CB34. Beklenti kopya sayısı azaldığı için sanal elmasın daha küçük çıkması yönündeydi, ama ölçüm bunu doğrulamıyor: false. Canli’nin tek bir int üyesi olduğu için kopyanın bedeli zaten küçük (birkaç bayt); buna karşılık Yuzucu ve Kosucu’nun her biri, kendi Canli’sine nasıl ulaşacağını çözmek için ayrı bir kayıt (sanal taban işaretçisi ya da benzeri bir mekanizma, derleyiciye göre değişir) taşıyor — iki taban, iki ayrı kayıt. Bu derleyicide ek kayıtların toplam bedeli, tasarruf edilen tek Canli kopyasından daha ağır bastı. Sanal elmasın tek bir Canli‘den büyük olması (true) zaten bekleniyordu; asıl öğretici olan, “daha az kopya” ile “daha küçük nesne”nin aynı şey olmadığıdır. Bu, CB26’nın sanal işlevler için ölçtüğü “vptr her nesneye ekleniyor” bulgusunun çoklu kalıtım bağlamındaki karşılığı: paylaşılan bir şeyi (yöntem tablosu / taban alt nesnesi) tek elde tutmak, nesneye küçük ama sıfır olmayan bir muhasebe maliyeti yüklüyor — ve bu maliyet, ortadan kaldırılan kopyanın kendi boyutundan büyük de olabiliyor. Sınıf parça: ölçülen büyüklük farkı, t’nin sanal taban alt nesnesinin kendisinden değil, o alt nesneye giden yolu tutan ek parçalardan kaynaklanıyor.

Sınırlayıcı Ölçüm

Bu derste ölçülen sanal kalıtım, verinin (can_) paylaşımını ve sanal taban sınıfın kurucusunu yalnız en türemiş sınıfın çağırdığı kuralı gösterdi; ama ölçüm sanal kalıtımın taşıdığı ek dolaylılık maliyetini (her erişimin bir sanal taban işaretçisi üzerinden çözülmesi) ele almadı — yalnız adres eşitliğini ölçtü, çözüm yolunun kaç adım sürdüğünü değil. Triatletci’nin kendi sanal işlevleri olduğu, Yuzucu ve Kosucu’nun da birbirinden farklı aynı imzalı işlevler tanımladığı durum — adlandırma çakışması ve belirsiz yeniden tanımlama — bu dersin kapsamı dışında. Nitelenmiş erişim (t.Yuzucu::can_) yalnız sanal olmayan kalıtımda gösterildi; sanal kalıtımda paylaşılan tek kopyaya nitelenmiş adla erişmenin nasıl davrandığı — ki normal erişimden farksız olurdu, çünkü kopya zaten tek — bu ders ayrıca sınamadı.

Özet

  • Çoklu kalıtımda yalnız ilk taban sınıfın alt nesnesi türetilmiş nesneyle aynı adresi paylaşıyor; sonraki taban alt nesneleri farklı bir ofsette duruyor.
  • İki taban sınıfın ortak bir atası varsa ve kalıtım sanal değilse, o ata türetilmiş nesnenin içinde iki ayrı kopya olarak yer kaplıyor — elmas sorunu.
  • sizeof, iki ayrı kopyanın varlığını bellek boyutunda doğruluyor; iki farklı yoldan ulaşılan ortak ata nesnesi farklı adresler taşıyor.
  • virtual public kalıtım, ortak atayı en tepedeki türetilmiş sınıfın paylaştığı tek bir alt nesneye indiriyor; iki yoldan ulaşılan ata artık aynı adresi taşıyor.
  • Bu dersin ölçtüğü ifade parça sınıfında: taban alt nesne türetilmiş nesnenin bir parçası, ama çoklu ve elmas biçimli kalıtımda o parçanın adresi ve sayısı değişiyor.

Sonraki Adım

Bu derste ölçülen hep normal akışta tamamlanan kurucu ve yıkıcı çağrılarıydı; hiçbir kurucu bir hata fırlatmadı. Bir sonraki ders, İstisnalar, bir kurucunun yarıda kesildiği — bir hata fırlattığı — durumu ölçüyor: yapım tamamlanmadan fırlatılan bir istisna, o nesnenin zaten kurulmuş parçalarının yıkımını nasıl etkiliyor ve bir istisna nesnesi throw ile fırlatılıp catch ile yakalanırken bir kopya mı çıkarıyor. Ölçülen yine bir ifadenin hangi nesneyle çalıştığı, ama bu kez soru kurma sürecinin ortasında kesilen bir nesnenin durumu.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat