---
title: 'Çoklu Kalıtım ve Elmas Sorunu'
source: 'https://academia.sh/tr/kurslar/cpp-temelleri/coklu-kalitim-ve-elmas-sorunu'
course: 'C++ Temelleri ve Nesne Tasarımı'
language: tr
updated: '2026-09-26T03:02:46+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Ç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'
---

# Ç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ü.

Ö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?

```cpp
// 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ı?

```cpp
// 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?

```cpp
// 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?

```cpp
// 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?

```cpp
// 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?

```cpp
// 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.
