---
title: 'Statik Çok Biçimlilik'
source: 'https://academia.sh/tr/kurslar/cpp-sablonlar/statik-cok-bicimlilik'
course: 'Şablonlar ve Başarım'
language: tr
updated: '2026-10-21T05:02:46+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'CRTP ile bir taban sınıf şablonundan türeyen sınıfa derleme zamanında gönderimi kurar.'
    - 'Statik ve dinamik çok biçimliliğin bedelini hangi sınıfta ödediğini ayırt eder.'
    - 'Politika sınıfı ve mixin ile davranışı derleme zamanında bileşenlere böler.'
    - 'Farklı CRTP sınıflarının ortak tabanla tek koleksiyona konamayacağını ve variant ile seçimin koşuma döndüğünü gösterir.'
---

# Statik Çok Biçimlilik

CRTP ile derleme zamanında gönderim, sanal işlevli sürümle karşıtlık, politika sınıfları ve mixin, kavramla kısıtlanmış statik arayüz ve variant ile koşuma dönen seçim.

Önceki ders aday elemesini derlemeye taşıdı: uygun olmayan şablon hata vermeden listeden
düştü. Bu ders aynı fikri bir sınıf hiyerarşisine uyguluyor. Bir taban sınıf, hangi
türemiş sınıfla çalışacağını derleme sırasında biliyorsa, çağrıyı çalışırken seçmeye
gerek kalmaz. Bu **statik çok biçimlilik**tir. Sorumuz, bu seçimin bedelini kimin
ödediği: karar derlemede verildiğinde derleyiciye ne eklenir, koşumdan ne düşer?

M11/K04, dinamik çok biçimliliği sanal işlevlerle ele aldı: tek bir arayüz, çalışırken
seçilen gövde. M10/K03'te Rust'ta görülen "her tip için ayrı gövde" de aynı kavramdır; bu
ders iki dili karşılaştırmaz. Bu ders bir üstünlük de kurmaz. İki yöntem de tek bir çağrı için farklı bir
yerde ödeme yapar. Statik tarafta her türemiş tip için ayrı bir gövde yazılır ve bu bedel
**derleme** sınıfındadır; dinamik tarafta gövdeleri derleyici tip başına üretmez, yalnız
yazılan türemiş sınıflar kadar gövde vardır ve seçim çalışırken yapılır.

## CRTP: Tabanın Türemiş Sınıfı Bilmesi

**CRTP** (Curiously Recurring Template Pattern, tuhaf biçimde yinelenen şablon örüntüsü)
şunu yapar: taban sınıf bir sınıf şablonudur ve parametresi türemiş sınıfın kendisidir.
`Kare`, `Sekil<Kare>`'den türer. Taban, `static_cast<const D&>(*this)` ile kendini türemiş
tipe çevirir ve türemişin işlevini çağırır. Sanal işlev yoktur: hangi gövdenin
çağrılacağı derlemede bellidir.

```cpp
// sekil.hpp — CRTP tabani ve iki turemis sinif
#ifndef SEKIL_HPP
#define SEKIL_HPP

inline int govde = 0;

template <typename D>
struct Sekil {
    long alan() const {
        static const bool kayit = (++govde, true);
        (void)kayit;
        return static_cast<const D&>(*this).alan_hesapla();
    }
};

struct Kare : Sekil<Kare> {
    long kenar;
    explicit Kare(long k) : kenar(k) {}
    long alan_hesapla() const { return kenar * kenar; }
};

struct Dikdortgen : Sekil<Dikdortgen> {
    long en;
    long boy;
    Dikdortgen(long e, long b) : en(e), boy(b) {}
    long alan_hesapla() const { return en * boy; }
};

#endif
```

```cpp
// crtp.cpp — crtp ile gonderim
#include <cstdio>

#include "sekil.hpp"

int main() {
    Kare a(3);
    Dikdortgen b(2, 5);
    Kare c(4);
    long toplam = a.alan() + b.alan() + c.alan();
    std::printf("toplam=%ld govde=%d\n", toplam, govde);
    return 0;
}
```

```text
toplam=35 govde=2
```

**MP32.** Toplam `9 + 10 + 16 = 35` olur. `Sekil<D>::alan` iki farklı tip için
somutlaştırıldığından iki gövde vardır: `Sekil<Kare>::alan` ve `Sekil<Dikdortgen>::alan`.
Üçüncü çağrı (`Kare(4)`) ilk gövdeyi yeniden kullanır. Çekirdekteki `crtp ile gonderim`
satırı bu sayıdır: çağrı sayısı üç, gövde sayısı iki. Bedel **derleme** sınıfına taşınmıştır
ve yanında ürün sayısını taşır.

Bu düzende `Sekil<D>` tek bir sınıf değil bir aileye ait ayrı sınıflardır; `Sekil<Kare>` ile
`Sekil<Dikdortgen>` arasında bir türetme ilişkisi yoktur. `static_cast<const D&>(*this)`
yalnız nesne gerçekten bir `D` ise doğrudur. `D`, `Sekil<D>`'den hiç türemiyorsa dönüşüm
derlemede reddedilir. Ama bir sınıf `Sekil<B>`'den türeyip `B` olarak başka bir türemiş
sınıfı yazarsa, derleyici bunu yakalayamaz: nesne gerçekte o tip değildir ve davranış
tanımsızdır. Bu yüzden taban sınıfın yapıcısını
korumalı yapmak ve türemişi `final` işaretlemek yaygın savunmalardır.

## Sanal İşlevli Karşılığı

Aynı hesabı sanal işlevle yazalım. Tek bir taban sınıf ve iki türemiş sınıf vardır; gövdeler
bir kez yazılır ve çağrı taban referansı üzerinden, çalışırken seçilir. Bu seçim genellikle
sanal işlev tablosu denen bir bellek yapısıyla gerçeklenir; burada boyutunu ya da içeriğini
basmıyoruz, çünkü ölçtüğümüz şey
gövde ve çağrı sayısıdır.

```cpp
// sanal.cpp — sanal islevle ayni hesap
#include <cstdio>
#include <memory>
#include <vector>

inline int sanal_govde = 0;
inline int dolayli_cagri = 0;

struct SanalSekil {
    virtual ~SanalSekil() = default;
    virtual long alan() const = 0;
};

struct SanalKare : SanalSekil {
    long kenar;
    explicit SanalKare(long k) : kenar(k) {}
    long alan() const override {
        static const bool kayit = (++sanal_govde, true);
        (void)kayit;
        return kenar * kenar;
    }
};

struct SanalDikdortgen : SanalSekil {
    long en;
    long boy;
    SanalDikdortgen(long e, long b) : en(e), boy(b) {}
    long alan() const override {
        static const bool kayit = (++sanal_govde, true);
        (void)kayit;
        return en * boy;
    }
};

int main() {
    std::vector<std::unique_ptr<SanalSekil>> v;
    v.push_back(std::make_unique<SanalKare>(3));
    v.push_back(std::make_unique<SanalDikdortgen>(2, 5));
    v.push_back(std::make_unique<SanalKare>(4));
    long toplam = 0;
    for (const auto& s : v) {
        ++dolayli_cagri;
        toplam += s->alan();
    }
    std::printf("toplam=%ld govde=%d dolayli=%d\n", toplam, sanal_govde, dolayli_cagri);
    return 0;
}
```

```text
toplam=35 govde=2 dolayli=3
```

**MP33.** Sonuç aynı, toplam 35 ve gövde sayısı yine iki; çünkü iki türemiş sınıf iki gövde
yazar. Fark, gövdelerin nasıl seçildiğindedir: burada üç çağrının üçü de taban referansı
üzerinden dolaylıdır (`dolayli=3`) ve hangi gövdenin çalışacağı çalışırken belirlenir. CRTP
sürümünde üç çağrının hiçbiri dolaylı değildir. Bu iki satır sınıfları ayırır: dolaylı
çağrı sayısı **koşum**, her tip için ayrı gövde **derleme** sınıfına yazılır. İkisi de bir
ürün sayısıdır; hangisinin daha hızlı olduğu bu derste iddia edilmez.

## Bileşenlere Bölmek: Politika ve Mixin

CRTP'nin ikinci kullanımı, bir sınıfa davranış eklemektir. **Mixin** adı verilen bu
kullanımda taban sınıf şablonu türemiş sınıfa bir işlev ekler; türemiş sınıf tabanı seçerek
kendi arayüzünü kurar. **Politika sınıfı** ise bir şablon parametresi olarak verilen ve
davranışın bir parçasını belirleyen sınıftır. Her ikisi de seçimi derlemede yapar.

```cpp
// mixin.cpp — mixin ve politika sinifi
#include <cstdio>

template <typename D>
struct Yazdirilabilir {
    void yaz() const {
        std::printf("%s alan=%ld\n", D::ad(), static_cast<const D&>(*this).alan());
    }
};

struct MetreOlcu {
    static long cevir(long v) { return v * 100; }
};

struct SantimOlcu {
    static long cevir(long v) { return v; }
};

template <typename Olcu>
struct Kare : Yazdirilabilir<Kare<Olcu>> {
    long kenar;
    explicit Kare(long k) : kenar(k) {}
    static const char* ad() { return "kare"; }
    long alan() const { return Olcu::cevir(kenar) * Olcu::cevir(kenar); }
};

int main() {
    Kare<SantimOlcu> a(3);
    Kare<MetreOlcu> b(3);
    a.yaz();
    b.yaz();
    return 0;
}
```

```text
kare alan=9
kare alan=90000
```

**MP34.** `Yazdirilabilir<D>` `yaz` işlevini ekler; `Kare<Olcu>` hem mixin tabanı hem de
politika parametresi alır. `SantimOlcu` ile alan 9, `MetreOlcu` ile 90000 çıkar. Her
`Olcu` seçimi ayrı bir `Kare` ve ayrı bir `yaz` gövdesi yazdırır. Politika ne kadar çok
bileşene bölünürse gövde sayısı, kullanılan bileşim sayısı kadar çarpılabilir; bu bileşimin bedeli **derleme** sınıfında
birikir ve çalışırken bir seçim kalmaz.

## Kavramla Kısıtlanmış Statik Arayüz

CRTP'de arayüz örtüktür: taban `alan_hesapla` çağırır, ama türemiş sınıfta bu işlev yoksa
hata taban sınıf gövdesinin içinde çıkar ve derleyiciler onu genellikle uzun bir
somutlaştırma iletisi olarak gösterir. C++20
kavramları arayüzü açıkça yazar. Aşağıdaki işlev, `alan()` çağrısı geçerli olan ve
sonucu `long`'a dönüşebilen her tipi kabul eder ve çağrı noktasında kısıtı denetler.

```cpp
// arayuz.cpp — kavramla kisitlanmis statik arayuz
#include <concepts>
#include <cstdio>

#include "sekil.hpp"

template <typename T>
concept AlanliSekil = requires(const T& t) {
    { t.alan() } -> std::convertible_to<long>;
};

template <AlanliSekil T>
long ikiyle(const T& s) { return s.alan() * 2; }

struct Alansiz {};

static_assert(AlanliSekil<Kare>);
static_assert(AlanliSekil<Dikdortgen>);
static_assert(!AlanliSekil<Alansiz>);

int main() {
    Kare k(3);
    Dikdortgen d(2, 5);
    std::printf("ikiyle %ld %ld\n", ikiyle(k), ikiyle(d));
    return 0;
}
```

```text
ikiyle 18 20
```

**MP35.** `Kare` ve `Dikdortgen` kavramı `alan` üyesi üzerinden sağlar; `Alansiz` sağlamaz ve
`static_assert` bunu derlemede doğrular. Kavram, çağrıyı yapan tarafa bir sözleşme
kazandırır: kısıt sağlanmazsa hata çağrı noktasında çıkar ve derleyiciler genellikle kısıtın adını
gösterir. Ek olarak,
kavram bir ürün sayısı eklemez; denetim derleme sırasında biter ve çalışan koda hiçbir şey
yazmaz.

## Varsayılan Davranış ve Gizleme

Sanal işlevde türemiş sınıf, taban sınıfın bir işlevini aynı imzayla yeniden tanımlar;
CRTP'de ise düzen farklıdır. Tabanda varsayılan bir gövde yazılır, türemiş sınıf aynı
adla bir işlev tanımlarsa onu gizler ve taban işlev, çağrıyı türemişe yönlendirdiği için
türemişin gövdesini görür. Her şey derlemede çözülür: çağrı hangi adın kapsamından
gelirse o gövde seçilir. Aşağıda `Betimli` tabanı varsayılan bir `betimle` yazar; `Daire`
kendi sürümünü tanımlar ve tabandaki çağrı ona gider.

```cpp
// varsayilan.cpp — tabanda varsayilan, turemiste gizleme
#include <cstdio>

template <typename D>
struct Betimli {
    const char* betimle() const {
        return static_cast<const D&>(*this).ad();
    }
    const char* ad() const { return "sekil"; }
};

struct Ucgen : Betimli<Ucgen> {};

struct Daire : Betimli<Daire> {
    const char* ad() const { return "daire"; }
};

int main() {
    Ucgen u;
    Daire d;
    std::printf("ucgen %s\n", u.betimle());
    std::printf("daire %s\n", d.betimle());
    return 0;
}
```

```text
ucgen sekil
daire daire
```

**MP36.** `Ucgen` kendi `ad` işlevini tanımlamadığı için tabanın varsayılanı bulunur ve
`betimle` `sekil` döndürür. `Daire` aynı adla bir işlev tanımladığı için
`static_cast<const D&>` üzerinden `daire` bulunur. İkisi de derlemede çözülür; yani tabandaki varsayılan,
yalnız türemiş sınıfın yazmadığı yer için gövde yazdırır. Çalışırken yapılan seçimin yerine burada
derlemedeki ad araması çalışır.

## Her Tip İçin Ayrı Statik Üye

Sınıf şablonunun her somutlaştırması kendi statik üyesini taşır. CRTP'de bu, her türemiş
sınıfa kendi sayacını vermek için kullanılır: sayaç tabanın içinde durur, ama `Sayilan<Kare>`
ile `Sayilan<Dikdortgen>` ayrı sınıflar olduğu için sayaçlar da ayrıdır. Şablon
olmayan bir tabanda tek bir sayaç bütün türemişleri toplardı.

```cpp
// sayilan.cpp — tip basina sayac
#include <cstdio>

template <typename D>
struct Sayilan {
    static inline int adet = 0;
    Sayilan() { ++adet; }
    Sayilan(const Sayilan&) { ++adet; }
    ~Sayilan() { --adet; }
};

struct Kare : Sayilan<Kare> {};
struct Dikdortgen : Sayilan<Dikdortgen> {};

int main() {
    Kare a;
    Kare b;
    Dikdortgen c;
    std::printf("kare=%d dikdortgen=%d\n", Sayilan<Kare>::adet, Sayilan<Dikdortgen>::adet);
    {
        Kare gecici = a;
        std::printf("kare=%d\n", Sayilan<Kare>::adet);
    }
    std::printf("kare=%d\n", Sayilan<Kare>::adet);
    return 0;
}
```

```text
kare=2 dikdortgen=1
kare=3
kare=2
```

**MP37.** İki `Kare` ve bir `Dikdortgen` oluşturulunca sayaçlar ayrı sayılır: iki ve bir.
Kopya, kapsama girince sayacı üçe çıkarır, kapsamdan çıkınca iki olur. Ortak sayaç
isteyen bir tasarım için tabanın şablon olmaması gerekir; ayrı sayaç istendiğinde
CRTP bunu tek satırla verir. Bu davranış her türemiş sınıf için ayrı bir sayaç nesnesi
yazılması demektir ve bedel **derleme** sınıfına yazılır, koşumda yalnız sayaç artışı kalır.

## Hangisi Ne Zaman

Bu karşılaştırma bir sıralama değil bir kılavuzdur. Tip kümesi derlemede kapalıysa ve her
tip için gövdeler derleyiciye yazdırılabiliyorsa CRTP, gönderimi dolaylı çağrısız kurar;
ama farklı tipleri tek bir koleksiyonda taşımaz. Tip kümesi çalışırken büyüyorsa ya da
farklı tipler tek bir kapta tutulacaksa sanal işlevli sürüm gerekir; gövdeler tip başına
üretilmez ama her çağrı çalışırken seçilir. Kapalı ama karışık bir küme için `std::variant`
ikisinin arasında durur: tek koleksiyon verir, seçimi koşuma bırakır. Üç yöntem de aynı
alan toplamını verdi (35); fark, sayının hangi sınıfta ödendiğidir.

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

CRTP'nin sınırı, türemiş sınıfların ortak bir tabanının olmamasıdır. `Sekil<Kare>` ile
`Sekil<Dikdortgen>` ayrı tiplerdir; birinin işaretçisi diğerinin işaretçisine dönüşmez.
Bu yüzden `Kare` ve `Dikdortgen` tek bir koleksiyona CRTP ile konamaz. Tek koleksiyon
gerekiyorsa, kapalı bir tip kümesi için `std::variant<Kare, Dikdortgen>` ve `std::visit`
kullanılır. Hangi alternatifin tutulduğu çalışırken bir indekste durur, yani seçim koşuma
döner.

```cpp
// degisken.cpp — variant ile tek koleksiyon
#include <cstdio>
#include <variant>
#include <vector>

#include "sekil.hpp"

int main() {
    std::vector<std::variant<Kare, Dikdortgen>> v;
    v.emplace_back(Kare(3));
    v.emplace_back(Dikdortgen(2, 5));
    v.emplace_back(Kare(4));
    int secim = 0;
    long alan = 0;
    for (const auto& s : v) {
        ++secim;
        alan += std::visit([](const auto& x) { return x.alan(); }, s);
    }
    std::printf("secim=%d alan=%ld\n", secim, alan);
    return 0;
}
```

```text
secim=3 alan=35
```

**MP38.** Üç eleman, üç `std::visit` çağrısı: `secim=3`, toplam `alan=35`. Gövdeler yine
`Sekil<Kare>::alan` ve `Sekil<Dikdortgen>::alan` olarak derlenmiştir, ama hangisinin
çağrılacağı artık her eleman için çalışırken bir indekse bakılarak seçilir. Yani
`variant`, CRTP'nin derlemede aldığı kararı koşuma geri verir; bu sürümde bedel **koşum**
sınıfına kaydı. Tek koleksiyon, kapalı bir tip kümesi ve seçimin ödenmesi birlikte gelir.
Küme açık olmalıysa (sonradan yeni tipler eklenecekse) sanal işlevli sürüm uygundur.

## Özet

- Bu dersin ölçtüğü düzenek bedeli **derleme** sınıfına taşır: CRTP ile üç çağrı iki
  gövdeyle yapılır (`toplam=35 govde=2`) ve hiçbiri dolaylı değildir.
- CRTP'de taban sınıf şablonu türemiş sınıfı bilir; çağrı `static_cast` ile türemişe gider.
- Sanal işlevli sürüm aynı toplamı verir, ama üç çağrının üçü de çalışırken seçilir
  (`dolayli=3`); iki yöntem farklı sınıflarda öder, üstünlük kurulmaz.
- Mixin ve politika sınıfları davranışı derlemede bileşenlere böler; her bileşim ayrı gövde
  yazdırır. Kavram, statik arayüzü açıkça ve çağrı noktasında denetler.
- Farklı CRTP sınıfları ortak bir tabanla tek koleksiyona konamaz; `variant` ile seçim koşuma döner
  (`secim=3 alan=35`).

## Sonraki Adım

Bu derste statik gönderim, hangi gövdenin çalışacağını derlemeye taşıdı; bedel gövde
sayısında ve yazılan koda yansıdı. Ama tek bir iş parçacığında kalmak şart değil: kilit
ile korunan bir sayaç, her güncellemede kaç eşgüdüm noktası ekliyor?
