---
title: 'Soyut Sınıflar ve Arayüzler'
source: 'https://academia.sh/tr/kurslar/cpp-temelleri/soyut-siniflar-ve-arayuzler'
course: 'C++ Temelleri ve Nesne Tasarımı'
language: tr
updated: '2026-09-26T03:02:46+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Salt sanal bir işlevin taban sınıftan doğrudan nesne kurulmasını nasıl engellediğini açıklar'
    - 'Taban sınıfın devraldığı gövdeli bir işlevin bile hep türetilmiş nesneyle çalıştığını ölçer'
    - 'Soyut sınıf ile veri taşımayan bir arayüz sınıfı arasındaki farkı örnekle ayırır'
---

# Soyut Sınıflar ve Arayüzler

Salt sanal bir işlevin taban sınıftan hiçbir örnek kurulamamasını nasıl garanti ettiğinin ve bir arayüzün, veri taşımayan salt sözleşmeden ibaret olduğunun ölçümü.

Önceki ders, `Sekil::tanit`'in bir gövdesi olduğunu ve türetilmiş sınıflar onu yeniden
tanımlamasa da çağrılabildiğini gösterdi. Bu ders, bir taban sınıfın hiç gövdesi
olmayan, yalnız bir sözleşme bildiren işlevlerle kurulduğu durumu ele alıyor: **salt
sanal işlev** (`= 0` ile işaretlenen). Böyle bir işlevi taşıyan sınıftan doğrudan nesne
kurulamıyor — yalnız onu **tam olarak** dolduran türetilmiş sınıflardan. Ölçü sorusu
yine aynı: bir taban işaretçisi ya da referansı üzerinden yapılan bir çağrı hangi
nesneyle çalışıyor — ama bu kez taban tipin kendi başına hiçbir örneği yok.

## Taban Sınıftan Nesne Kurulamıyor, Yalnız Türetilmişlerden

`Araci`'nin `ayni_mi` işlevi salt sanal (`= 0`); bu, `Araci`'den doğrudan bir nesne
kurmayı derleme hatasına çeviriyor — `Araci a;` yazılamaz. Yalnız `ayni_mi`'yi dolduran
`Otomobil` ve `Bisiklet` gibi sınıflardan nesne kurulabiliyor. Bir `Araci*` dizisindeki
her eleman, yine kendi gösterdiği tikel nesneye mi ulaşıyor?

```cpp
// soyut_taban.cpp — salt sanal isleve sahip sinifin ornegi kurulamiyor, yalnizca dolduranlarin
#include <iostream>

class Araci {
public:
    virtual ~Araci() = default;
    virtual bool ayni_mi(const void* adres) const = 0;
};

class Otomobil : public Araci {
public:
    bool ayni_mi(const void* adres) const override {
        return static_cast<const void*>(this) == adres;
    }
};

class Bisiklet : public Araci {
public:
    bool ayni_mi(const void* adres) const override {
        return static_cast<const void*>(this) == adres;
    }
};

int main() {
    Otomobil o;
    Bisiklet b;
    Araci* araclar[2] = { &o, &b };

    std::cout << std::boolalpha;
    std::cout << "araclar[0] kendi nesnesi o ile ayni mi "
              << araclar[0]->ayni_mi(&o) << "\n";
    std::cout << "araclar[1] kendi nesnesi b ile ayni mi "
              << araclar[1]->ayni_mi(&b) << "\n";
    std::cout << "araclar[0] b ile ayni mi (capraz denetim) "
              << araclar[0]->ayni_mi(&b) << "\n";

    return 0;
}
```

```
araclar[0] kendi nesnesi o ile ayni mi true
araclar[1] kendi nesnesi b ile ayni mi true
araclar[0] b ile ayni mi (capraz denetim) false
```

**CB7.** `Araci a;` yazılsaydı derleyici reddederdi — salt sanal `ayni_mi` `Araci`'yi
tek başına eksik bırakıyor, yalnız `Otomobil` ve `Bisiklet` gibi işlevi tam dolduran
sınıflar kurulabilir. Bu yüzden bir `Araci*`'nin gösterebileceği tek şey, `ayni_mi`'yi
dolduran somut bir sınıfın örneğidir — taban sınıfın kendisine ait, "yedek" bir örneğe
düşme ihtimali hiç yok. `araclar[0]->ayni_mi(&o)` **true**, `araclar[1]->ayni_mi(&b)`
**true**, çapraz denetim **false**: her eleman kendi somut nesnesine ulaşıyor. (M08/K02'de
sorulan soru çağrıyı hangi tipin çözdüğüydü; burada taban tipin kendi başına hiç örneği
olmadığından çözüm hep somut bir türe düşüyor — ve Java'da bir arayüz referansı da hiçbir
zaman salt arayüz tipinde bir nesneye işaret edemez, C++'ta salt sanal işlev aynı
kısıtlamayı sağlıyor.) Sınıf **kendisi**: çağrı, taban tipin kendisi hiç var olamadığı
için, zorunlu olarak somut bir türetilmiş nesneyle çalışıyor.

## Devralınan Gövde de Aynı Nesneyle Çalışıyor

`Araci`, salt sanal `ayni_mi`'nin yanında gövdeli bir `bakim_bilgisi` de taşıyor.
`Otomobil` bunu yeniden tanımlamıyor, `Bisiklet` tanımlıyor. Devralınan gövde
çalıştığında `this` yine çağrılan nesnenin adresini mi taşıyor?

```cpp
// soyut_govdeli_isliv.cpp — devralinan govdeli islev de cagrilan nesnenin adresini tasiyor
#include <iostream>

class Araci {
public:
    virtual ~Araci() = default;
    virtual bool ayni_mi(const void* adres) const = 0;
    virtual void bakim_bilgisi(const void* beklenen) const {
        std::cout << "Araci::bakim_bilgisi calisan nesne beklenenle ayni mi "
                  << std::boolalpha << (static_cast<const void*>(this) == beklenen) << "\n";
    }
};

class Otomobil : public Araci {
public:
    bool ayni_mi(const void* adres) const override {
        return static_cast<const void*>(this) == adres;
    }
};

class Bisiklet : public Araci {
public:
    bool ayni_mi(const void* adres) const override {
        return static_cast<const void*>(this) == adres;
    }
    void bakim_bilgisi(const void* beklenen) const override {
        std::cout << "Bisiklet::bakim_bilgisi calisan nesne beklenenle ayni mi "
                  << std::boolalpha << (static_cast<const void*>(this) == beklenen) << "\n";
    }
};

int main() {
    Otomobil o;
    Bisiklet b;
    Araci* a1 = &o;
    Araci* a2 = &b;

    a1->bakim_bilgisi(&o);
    a2->bakim_bilgisi(&b);

    return 0;
}
```

```
Araci::bakim_bilgisi calisan nesne beklenenle ayni mi true
Bisiklet::bakim_bilgisi calisan nesne beklenenle ayni mi true
```

**CB8.** `Otomobil`, `bakim_bilgisi`'yi yeniden tanımlamadığı için `a1->bakim_bilgisi(&o)`
çağrısı `Araci::bakim_bilgisi`'nin gövdesini çalıştırıyor — ama bu gövde içinde bile
`this`, `o`'nun adresiyle karşılaştırıldığında **true** dönüyor. `Bisiklet` kendi
gövdesini sağladığı için `a2->bakim_bilgisi(&b)` `Bisiklet::bakim_bilgisi`'ni çalıştırıyor,
`this` yine `b`'nin adresi. Devralınan bir gövdenin çalışması, `Araci`'nin kendi başına
bir örneğinin var olduğu anlamına gelmiyor — o gövde yalnız somut bir `Otomobil`
nesnesi üzerinde çalıştırılıyor. Sınıf **kendisi**: taban sınıfın sağladığı ortak gövde
bile, çağrıldığı somut nesneden başka hiçbir nesneyle çalışmıyor.

## Bir Arayüz: Yalnızca Sözleşme, Ortak Veri Yok

`Araci` veri taşımıyordu ama ortak bir gövde taşıyordu — tam bir soyut sınıftı.
`Yazdirilabilir` bundan farklı: hiç veri alanı, hiç gövdeli işlev yok, yalnız bir
sözleşme. `Rapor` bu sözleşmeyi dolduruyor ve kendi verisini taşıyor.

```cpp
// arayuz_sozlesme.cpp — arayuz hic veri tasimiyor, sozlesmeyi dolduran kendi verisini tasiyor
#include <iostream>
#include <string>
#include <utility>

class Yazdirilabilir {
public:
    virtual ~Yazdirilabilir() = default;
    virtual bool ayni_mi(const void* adres) const = 0;
};

class Rapor : public Yazdirilabilir {
public:
    explicit Rapor(std::string baslik) : baslik_(std::move(baslik)) {}
    bool ayni_mi(const void* adres) const override {
        return static_cast<const void*>(this) == adres;
    }
    const std::string& baslik() const { return baslik_; }
private:
    std::string baslik_;
};

void listele(const Yazdirilabilir& y, const void* beklenen) {
    std::cout << std::boolalpha
              << "listele icinde cagrilan nesne beklenenle ayni mi "
              << y.ayni_mi(beklenen) << "\n";
}

int main() {
    Rapor r("Yillik Rapor");
    std::cout << "raporun basligi " << r.baslik() << "\n";
    listele(r, &r);
    return 0;
}
```

```
raporun basligi Yillik Rapor
listele icinde cagrilan nesne beklenenle ayni mi true
```

**CB9.** `Yazdirilabilir` hiçbir veri alanı bildirmiyor, `baslik_` tümüyle `Rapor`'a ait —
`Yazdirilabilir` bu veriye hiç karışmıyor, yalnız `ayni_mi` sözleşmesini dayatıyor. `r`,
`Yazdirilabilir&`'a bağlanıp `listele` içine geçirildiğinde `y.ayni_mi(&r)` yine **true**
dönüyor: referans bağlama burada da bir kopya kurmuyor, `y` `main` içindeki `r` ile aynı
nesne. `Araci` ile `Yazdirilabilir` arasındaki fark somut: `Araci` veri taşıyabilir ve
ortak gövdeli işlevler sunabilir (soyut sınıf), `Yazdirilabilir` yalnızca imzalardan
ibaret, hiç veri hiç gövde yok (arayüz). Sınıf **kendisi**: ikisinde de çağrı hep
sözleşmeyi dolduran somut nesneyle çalışıyor.

## Salt Sanal İşlevin Gövdesi Olabilir, Ama Yalnız Nitelenmiş Çağrıyla

`= 0` bir işlevi salt sanal yapıyor ve taşıyan sınıfın örneklenmesini engelliyor — ama
bu, işlevin hiç gövdesi olamayacağı anlamına gelmiyor. `SesliArac::uyar` hem salt sanal
hem de bir gövdeye sahip. Bu gövdeye normal bir sanal çağrıyla mı, yoksa başka bir yolla
mı ulaşılıyor?

```cpp
// soyut_salt_sanalin_govdesi.cpp — salt sanal islevin govdesi olabilir, ama yalnizca nitelenmis cagriyla calisir
#include <iostream>

class SesliArac {
public:
    virtual ~SesliArac() = default;
    virtual void uyar() const = 0;
};

void SesliArac::uyar() const {
    std::cout << "SesliArac::uyar (varsayilan govde) calisti\n";
}

class Kamyon : public SesliArac {
public:
    void uyar() const override {
        std::cout << "Kamyon::uyar calisti, sonra taban govdeyi nitelenmis cagiriyor\n";
        SesliArac::uyar();
    }
};

int main() {
    Kamyon k;
    SesliArac& taban_referans = k;
    taban_referans.uyar();
    return 0;
}
```

```
Kamyon::uyar calisti, sonra taban govdeyi nitelenmis cagiriyor
SesliArac::uyar (varsayilan govde) calisti
```

**CB28.** `taban_referans.uyar()` sıradan bir sanal çağrı — ve `Kamyon` `uyar`'ı yeniden
tanımladığı için `SesliArac`'ın gövdesine kendiliğinden hiç ulaşmıyor, tıpkı önceki
derslerdeki her `override`'da olduğu gibi. `SesliArac::uyar`'ın gövdesi yalnız
`Kamyon::uyar`'ın içinden **nitelenmiş** bir çağrıyla (`SesliArac::uyar();`) elle
çağrıldığında çalışıyor — bu satır sanal gönderimi devre dışı bırakıp doğrudan o ismi
hedefliyor. "Salt sanal" ile "gövdesiz" burada ayrışıyor: `= 0`, sınıfın örneklenmesini
engelleyen ve türetilmiş sınıfları işlevi doldurmaya zorlayan şey; gövdenin olup
olmaması ayrı bir soru, ve varsa bu gövdeye yalnız nitelenmiş çağrıyla erişilebiliyor,
olağan sanal gönderimle değil. Sınıf **kendisi**: nitelenmiş çağrı bile `k`'nin
kendisi üzerinde çalışıyor, yalnız hangi gövdenin çalıştırılacağını isim çözümü
belirliyor.

## Soyut Tabanın Örneği Kurulamıyor, Somut Taban Değerle Alınıp Dilimlenebiliyor

`Araci` salt sanal bir işlev taşıdığı için `Araci a;` hiç derlenmiyor — bu, `Araci`
tipinde bir **değer**in de hiçbir zaman var olamayacağı, dolayısıyla `Araci` için
nesne dilimleme riskinin bile oluşamayacağı anlamına geliyor. Peki soyut olmayan, gövdesi
tam bir taban sınıf söz konusu olduğunda durum nasıl değişiyor?

```cpp
// soyut_somut_taban_degerle_alma.cpp — soyut tabanin ornegi kurulamaz, somut taban ise degerle alinip dilimlenebilir
#include <iostream>
#include <string>

class TasitKaydi {
public:
    explicit TasitKaydi(std::string plaka) : plaka_(std::move(plaka)) {}
    std::string plaka() const { return plaka_; }
protected:
    std::string plaka_;
};

class AyrintiliKayit : public TasitKaydi {
public:
    AyrintiliKayit(std::string plaka, int km)
        : TasitKaydi(std::move(plaka)), km_(km) {}
    int km() const { return km_; }
private:
    int km_;
};

int main() {
    AyrintiliKayit ayrintili("34ABC123", 15000);
    TasitKaydi dilimlenmis = ayrintili;

    std::cout << std::boolalpha;
    std::cout << "dilimlenmis ile ayrintili'nin ayni adresi tasiyip tasimadigi "
              << (static_cast<void*>(&dilimlenmis) == static_cast<const void*>(&ayrintili)) << "\n";
    std::cout << "dilimlenmis nesnenin plakasi " << dilimlenmis.plaka() << "\n";

    return 0;
}
```

```
dilimlenmis ile ayrintili'nin ayni adresi tasiyip tasimadigi false
dilimlenmis nesnenin plakasi 34ABC123
```

**CB29.** `TasitKaydi` salt sanal işlev taşımıyor, somut bir sınıf — bu yüzden
`TasitKaydi dilimlenmis = ayrintili;` derleniyor ve Kalıtım dersindeki CB21'de ölçülen
dilimlemenin aynısını üretiyor: `dilimlenmis`, `ayrintili`'den bağımsız, kendi adresinde
yaşayan ayrı bir nesne (**false**), ve `AyrintiliKayit`'in eklediği `km_` alanı hiç
taşınmıyor. `Araci` için bu satırın bir karşılığı yok, çünkü `Araci` tipinde bir
değişken tanımlamanın kendisi derleme hatası — dilimlemenin hedefi olacak bir `Araci`
değeri hiç var olamıyor. Soyutluk, yalnız "yeniden tanımlama zorunluluğu" değil, aynı
zamanda "taban tipte hiçbir zaman bir değer oluşamaması" anlamına geliyor: soyut bir
taban, işaretçi ya da referansla erişilebilir ama asla bir değişkene, parametreye ya da
dönüş değerine değerle konulamaz — bu da onu dilimlemeye karşı yapısal olarak bağışık
kılıyor. Dilimleme riski yalnız somut sınıflar için var. Sınıf **kopya**: `dilimlenmis`,
`ayrintili`'nin taban parçasının bağımsız bir kopyası.

## Arayüzün Rolü: Akraba Olmayan Tipleri Tek Sözleşme Altında İşlemek

`Rapor` ve `Fatura` birbirinden türemiyor, hiçbir veri ya da davranışı paylaşmıyorlar —
tek ortak noktaları `Yazdirilabilir` sözleşmesini doldurmaları. Bu ikisi aynı vektörde,
somut tipleri bilinmeden, tekdüze işlenebiliyor mu?

```cpp
// arayuz_rolu_coklu_tip.cpp — farkli somut tipler ayni arayuz uzerinden tekduze islenebiliyor
#include <iostream>
#include <string>
#include <vector>

class Yazdirilabilir {
public:
    virtual ~Yazdirilabilir() = default;
    virtual std::string metin() const = 0;
};

class Rapor : public Yazdirilabilir {
public:
    explicit Rapor(std::string baslik) : baslik_(std::move(baslik)) {}
    std::string metin() const override { return "Rapor: " + baslik_; }
private:
    std::string baslik_;
};

class Fatura : public Yazdirilabilir {
public:
    explicit Fatura(int tutar) : tutar_(tutar) {}
    std::string metin() const override { return "Fatura: " + std::to_string(tutar_); }
private:
    int tutar_;
};

int main() {
    Rapor r("Yillik Rapor");
    Fatura f(150);
    std::vector<Yazdirilabilir*> belgeler{ &r, &f };

    for (const auto* belge : belgeler) {
        std::cout << belge->metin() << "\n";
    }

    return 0;
}
```

```
Rapor: Yillik Rapor
Fatura: 150
```

**CB30.** `Rapor` ve `Fatura` aynı hiyerarşi ağacında akraba değiller, ortak hiçbir veri
alanı ya da gövdeli işlev paylaşmıyorlar — bağları yalnız ikisinin de `Yazdirilabilir`'i
doldurması. `belgeler` vektörü ikisini de tek bir `Yazdirilabilir*` tipi altında tutuyor
ve döngü, elemanın somut tipini hiç bilmeden `metin()`'i çağırıyor; her çağrı yine de
doğru somut tipin işlevine ulaşıyor: "Rapor: Yillik Rapor" ve "Fatura: 150". Bu,
arayüzün asıl rolünü gösteriyor: soyut sınıfın aksine, arayüz akrabalık kurmuyor, yalnız
imzaca bir sözleşme dayatıyor — sözleşmeyi dolduran her tip, aralarında başka hiçbir
ortaklık olmasa da aynı koleksiyonda, aynı döngüde işlenebiliyor. Sınıf **kendisi**:
her `metin()` çağrısı, döngüdeki elemanın işaret ettiği somut nesnenin ta kendisiyle
çalışıyor.

## Arayüzün de Sanal Bir Yıkıcıya İhtiyacı Var

`Yazdirilabilir` yalnız sözleşme sunuyor gibi görünse de, `Rapor` gibi somut bir
türetilmişi `Yazdirilabilir*` üzerinden `delete` edebilmek için kendi yıkıcısının da
sanal olması gerekiyor. Bu ders bunu doğruluyor mu?

```cpp
// arayuz_sanal_yikici.cpp — arayuzun sanal yikicisi turetilmis nesnenin tam yikim zincirini calistiriyor
#include <iostream>

class Yazdirilabilir {
public:
    virtual ~Yazdirilabilir() { std::cout << "Yazdirilabilir yikici\n"; }
    virtual void yaz() const = 0;
};

class Rapor : public Yazdirilabilir {
public:
    ~Rapor() override { std::cout << "Rapor yikici\n"; }
    void yaz() const override { std::cout << "Rapor::yaz\n"; }
};

int main() {
    Yazdirilabilir* belge = new Rapor();
    belge->yaz();
    std::cout << "-- silme basliyor --\n";
    delete belge;
    return 0;
}
```

```
Rapor::yaz
-- silme basliyor --
Rapor yikici
Yazdirilabilir yikici
```

**CB31.** `belge`'nin statik tipi `Yazdirilabilir*`, ama `delete belge` önce **Rapor
yikici**'yi, sonra **Yazdirilabilir yikici**'yi çalıştırıyor — tıpkı Sanal Fonksiyonlar
dersindeki CB24'te ölçülen ilkenin aynısı, burada arayüz bağlamında. `Yazdirilabilir`
salt sanal işlevler sunan bir sözleşme olsa da, yıkıcısı `virtual` olmasaydı aynı
`delete belge` yalnız `Yazdirilabilir`'in kendi yıkıcısını çağırırdı, `Rapor`'un
kaynakları hiç serbest bırakılmadan — ve statik/dinamik tip farklıyken sanal olmayan bir
yıkıcı üzerinden silme tanımsız davranış olurdu. Bu dal, CB24'te olduğu gibi,
deterministik bir çıktı vermeyeceği için burada çalıştırılmıyor. Sonuç: "yalnız sözleşme
sunmak" bir sınıfı sanal yıkıcı gereksiniminden muaf tutmuyor — taban işaretçi üzerinden
polymorphic silme yapılacaksa, arayüz de dâhil, yıkıcı sanal olmalı. Sınıf **kendisi**:
yıkım zinciri hep `belge`'nin işaret ettiği gerçek `Rapor` nesnesi üzerinde işliyor.

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

Bu derste ölçülen soyutluk **tekli kalıtım** altında: her somut sınıf tek bir soyut
taban sınıfı ya da arayüzü dolduruyordu. Bir sınıfın birden çok taban sınıftan
türeyerek birden çok sözleşmeyi aynı anda doldurduğu durum — çoklu kalıtım — bu dersin
kapsamı dışında; bir sonraki ders bunu ölçüyor. Salt sanal bir işlevin gövdesi de
olabileceği yalnız tek bir örnekle (`SesliArac::uyar`) gösterildi; o gövdenin hangi
tasarımlarda gerçekten gerektiği genellenmedi. `Araci a;` gibi bir satırın
gerçekten derleme hatası verdiği bu derste çalıştırılarak gösterilmedi, çünkü doğrulama
yalnız başarıyla derlenen kodu çalıştırıyor — bu, dersin metninde belirtilen ama koşulan
kod dışında kalan bir olgu.

## Özet

- Salt sanal bir işlev (`= 0`) taşıyan bir sınıftan doğrudan nesne kurulamıyor; yalnız
  o işlevi dolduran türetilmiş sınıflardan nesne kurulabiliyor.
- Bir `Araci*` dizisindeki her eleman kendi gösterdiği somut nesneye ulaşıyor, çünkü
  taban tipin kendi başına hiç örneği yok — sınıf **kendisi**.
- Taban sınıfın sağladığı devralınan, gövdeli bir işlev bile çağrıldığı somut nesneden
  başka hiçbir nesneyle çalışmıyor.
- Bir soyut sınıf (`Araci`) veri taşıyabilir ve ortak gövdeli işlevler sunabilir; bir
  arayüz (`Yazdirilabilir`) yalnızca imzalardan ibarettir, hiç veri hiç gövde taşımaz.
- Arayüz referansına geçirilen nesne, işlev içinde de dışarıdaki aynı nesne — referans
  bağlama burada da bir kopya kurmuyor.

## Sonraki Adım

Bu derste her somut sınıf tek bir soyut taban sınıfı ya da arayüzü dolduruyordu; taban
alt nesne hep türetilmiş nesnenin başında durdu, tıpkı Kalıtım dersindeki gibi. Bir
sonraki ders, Çoklu Kalıtım ve Elmas Sorunu, bir sınıfın **birden çok** taban sınıftan
aynı anda türediği durumu ölçüyor — orada ikinci ve sonraki taban alt nesneler artık
türetilmiş nesnenin başında durmayabiliyor, adresleri türetilmiş nesnenin adresiyle
çakışmayabiliyor. Ölçülen yine bir ifadenin hangi nesneyle çalıştığı, ama bu kez taban
alt nesnenin türetilmiş nesnenin **hangi parçasında** durduğu da sorunun bir parçası.
