---
title: 'Şablonlar ve Başarım'
source: 'https://academia.sh/tr/kurslar/cpp-sablonlar'
language: tr
updated: '2026-10-21T05:02:45+00:00'
license: 'CC BY-SA 4.0'
lessons: 12
outcomes:
    - 'Şablonlarla derleme zamanı çok biçimliliği kurar'
    - 'Bellek modeli ve eşzamanlılık ilkellerini doğru kullanır'
    - 'Başarım iddialarını profil ve ölçümle destekler'
    - 'Bir tekniğin bedeli derlemeye, koşuma, bellek düzenine ya da beklemeye taşıdığını ayırt eder ve o bedeli sayarak gösterir'
---

# Şablonlar ve Başarım

C++'ta bir bedel ortadan kalkmaz, yer değiştirir: kurs her düzeneği tek bir soruyla ölçer — bu teknik bedeli nereye taşıdı; derlemeye mi, koşuma mı, bellek düzenine mi, yoksa beklemeye mi? Şablonlar, özelleştirme, değişken sayıda parametre, tip özellikleri, SFINAE, statik çok biçimlilik, iş parçacıkları, bellek modeli, veri düzeni, profilleme, derleme süresi ve paket yönetimi aynı çerçeveden geçirilir.

## Şablon Metaprogramlama

Fonksiyon ve sınıf şablonları, özelleştirme, değişken sayıda şablon parametresi, tip özellikleri, aşırı yükleme çözümlemesi ve SFINAE, statik çok biçimlilik — her biri bedelin nereye taşındığı sorusuyla birlikte.

### Fonksiyon ve Sınıf Şablonları

Source: https://academia.sh/tr/kurslar/cpp-sablonlar/fonksiyon-ve-sinif-sablonlari

İşlev şablonu, şablon argüman çıkarımı, açık şablon argümanı ve sınıf şablonu; her yeni tip için derleyicinin yeni bir gövde üretmesi ve bu bedelin derlemeye taşınması.

Önceki kurs, `auto` ile tip çıkarırken işlemin kaynağa ne bıraktığını sordu. Bu kurs daha
geniş bir şeyi soruyor: soyutlama bedava değildir, bedel yalnız **yer değiştirir**. Bir
şablon koşumdaki tip kararını kaldırır, ama her yeni tip için derleyiciye yeni bir gövde
yazdırır. Bir kilit veri yarışını kaldırır, ama iş parçacıklarını sıraya sokar. Kurs her
düzeneğe tek bir soru yöneltir: bu düzenek bedeli nereye taşıdı?

#### Dört Sınıf

Kursun her ölçümü bir sınıfa düşer ve bu dört ad kurs boyunca değişmez.

| Sınıf | Bedel nerede ödenir |
|---|---|
| **derleme** | Derleyicinin işidir: koşumda karar adımı kalmaz, derleme bir ürün bırakır (üretilen gövde, seçilen aday) |
| **koşum** | Her çağrıda ya da her nesnede yeniden ödenir: sayılan adım ya da ayırma sıfırdan büyüktür |
| **bellek** | Verinin bellekteki düzenindedir: aynı iş, farklı sayıda depolama yuvasına dokunur |
| **bekleme** | İş parçacıkları arasındaki eşgüdümdedir: her eşgüdüm noktası biri ötekini bekleyebilir |

Sınıflar bir üst ayrımda ikiye toplanır. **Derleme** sınıfında bedel program koşmadan önce
ödenir; **koşum**, **bellek** ve **bekleme** sınıflarında program koşarken ödenir. Süre bu
kursta ölçülmez ve basılmaz; sayılan şey adım, gövde, yuva, geçiş ve eşgüdüm noktasıdır.

Bu soru M11/K01'deki soruyla karışmasın. K01 bir kararı **kimin verdiğini** sordu; burada
soru, bir düzeneğin bedelini kimin ödediği ve o bedelin neyle sayıldığıdır. K01'in `derleyici` sınıfı bir kararı adlandırırdı;
bu kursun **derleme** sınıfı bir bedeli adlandırır ve yanında daima bir ürün sayısı taşır.

#### Ölçüm Çekirdeği

Çekirdek üç dosyadır. `bedel.hpp` dört sınıfı bir `enum class` olarak tanımlar ve iki işlev
bildirir; `bedel.cpp` her ölçümü tek satır olarak basar, sınıf sayacını artırır ve özeti
verir.

```cpp
// bedel.hpp — bir duzenegin bedelini nerede odedigini adlandiran cekirdek
#ifndef BEDEL_HPP
#define BEDEL_HPP

#include <string>

enum class Bedel { derleme, kosum, bellek, bekleme };

// Olcum adini, sinifi ve gozlemi tek satirda basar, sinif sayacini artirir.
void raporla(const std::string& olcum, Bedel b, const std::string& gozlem);

// Sinif sayilarini ve ust ayrimi basar.
void ozet();

#endif
```

```cpp
// bedel.cpp — siniflama, satir bicimi ve sayac
#include "bedel.hpp"

#include <cstdio>

namespace {
int sayac[4] = {0, 0, 0, 0};
const char* const adlar[4] = {"derleme", "kosum", "bellek", "bekleme"};
}

void raporla(const std::string& olcum, Bedel b, const std::string& gozlem) {
    int i = static_cast<int>(b);
    ++sayac[i];
    std::printf("%-26s %-8s %s\n", olcum.c_str(), adlar[i], gozlem.c_str());
}

void ozet() {
    std::printf("siniflar: derleme %d, kosum %d, bellek %d, bekleme %d\n",
                sayac[0], sayac[1], sayac[2], sayac[3]);
    std::printf("derlemede odenen %d, kosumda odenen %d\n",
                sayac[0], sayac[1] + sayac[2] + sayac[3]);
}
```

```cpp
// tasindi.cpp — on iki duzenek, her biri icin tek soru: bedel nereye tasindi
#include <atomic>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>

#include "bedel.hpp"

static std::string say(const char* ad, long n) {
    return std::string(ad) + "=" + std::to_string(n);
}

/* 1. Sablonlar: her yeni tip yeni bir govde dogurur */
static int govde = 0;
template <typename T>
T kare(T x) {
    static const bool kayit = (++govde, true);
    (void)kayit;
    return x * x;
}
static void sablon() {
    const int sabit = 4;
    int cagri = 0;
    kare(2); ++cagri;
    kare(3); ++cagri;
    kare(1.5); ++cagri;
    kare(5L); ++cagri;
    kare(sabit); ++cagri;
    raporla("sablon somutlastirma", Bedel::derleme,
            say("cagri", cagri) + " " + say("govde", govde));
}

/* 2. Ozellestirme: bool paketi sekiz degeri tek yuvada tutar */
template <typename T>
struct Paket {
    T yuva[8] = {};
    const void* yer(int i) const { return &yuva[i]; }
};
template <>
struct Paket<bool> {
    unsigned char bitler = 0;
    const void* yer(int) const { return &bitler; }
};
template <typename T>
static int ayri_yuva(const Paket<T>& p) {
    int ayri = 0;
    for (int i = 0; i < 8; ++i) {
        bool yeni = true;
        for (int j = 0; j < i; ++j)
            if (p.yer(i) == p.yer(j)) yeni = false;
        if (yeni) ++ayri;
    }
    return ayri;
}
static void ozellestirme() {
    Paket<int> genel;
    Paket<bool> bitli;
    raporla("bool ozellestirmesi", Bedel::bellek,
            say("int", ayri_yuva(genel)) + " " + say("bool", ayri_yuva(bitli)));
}

/* 3. Degisken sayida parametre: ozyineli acma her adimda govde dogurur */
static int oz_govde = 0;
static long topla_oz() {
    static const bool kayit = (++oz_govde, true);
    (void)kayit;
    return 0;
}
template <typename Ilk, typename... Kalan>
long topla_oz(Ilk ilk, Kalan... kalan) {
    static const bool kayit = (++oz_govde, true);
    (void)kayit;
    return ilk + topla_oz(kalan...);
}
static int kat_govde = 0;
template <typename... T>
long topla_kat(T... x) {
    static const bool kayit = (++kat_govde, true);
    (void)kayit;
    return (0L + ... + x);
}
static void degisken() {
    long a = topla_oz(1, 2, 3);
    long b = topla_kat(1, 2, 3);
    raporla("ozyineli paket acma", Bedel::derleme,
            say("toplam", a == b ? a : -1) + " " + say("ozyineli", oz_govde) + " " +
                say("katlama", kat_govde));
}

/* 4. Tip ozellikleri: secim derlemede yapilir, kosumda sorgu kalmaz */
static int sorgu = 0;
template <typename T>
const char* tur(T) {
    if constexpr (std::is_integral_v<T>) return "tamsayi";
    else return "kayan";
}
static const char* tur_etiketli(bool tamsayi_mi) {
    ++sorgu;
    return tamsayi_mi ? "tamsayi" : "kayan";
}
static void tip_ozelligi() {
    int ayni = 0;
    ayni += std::string(tur(1)) == tur_etiketli(true);
    ayni += std::string(tur(2L)) == tur_etiketli(true);
    ayni += std::string(tur('a')) == tur_etiketli(true);
    ayni += std::string(tur(1.0)) == tur_etiketli(false);
    ayni += std::string(tur(1.0f)) == tur_etiketli(false);
    ayni += std::string(tur(2u)) == tur_etiketli(true);
    raporla("if constexpr ile secim", Bedel::derleme,
            say("ayni", ayni) + " sorgu=0 " + say("etiketli", sorgu));
}

/* 5. SFINAE: yerine koyma basarisizligi hata degil, elemedir */
template <typename T, typename = void>
struct bolunur : std::false_type {};
template <typename T>
struct bolunur<T, std::void_t<decltype(std::declval<T>() / 2)>> : std::true_type {};
static const char* evet_hayir(bool b) { return b ? "evet" : "hayir"; }
static void sfinae() {
    raporla("void_t ile algilama", Bedel::derleme,
            std::string("int=") + evet_hayir(bolunur<int>::value) +
                " string=" + evet_hayir(bolunur<std::string>::value));
}

/* 6. Statik cok bicimlilik: her turetilmis tip kendi govdesini alir */
static int crtp_govde = 0;
template <typename D>
struct Sekil {
    long alan() const {
        static const bool kayit = (++crtp_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, boy;
    Dikdortgen(long e, long b) : en(e), boy(b) {}
    long alan_hesapla() const { return en * boy; }
};
static void statik() {
    long toplam = Kare(3).alan() + Dikdortgen(2, 5).alan() + Kare(4).alan();
    raporla("crtp ile gonderim", Bedel::derleme,
            say("toplam", toplam) + " " + say("govde", crtp_govde));
}

/* 7. Is parcaciklari ve kilitler: her artis bir kilit alir */
static void kilitler() {
    std::mutex m;
    long deger = 0, alma = 0;
    std::vector<std::thread> isler;
    for (int i = 0; i < 4; ++i)
        isler.emplace_back([&] {
            for (int j = 0; j < 10000; ++j) {
                std::lock_guard<std::mutex> g(m);
                ++deger;
                ++alma;
            }
        });
    for (auto& t : isler) t.join();
    raporla("kilitli sayac", Bedel::bekleme, say("alma", alma) + " " + say("deger", deger));
}

/* 8. Bellek modeli: tek release yazisi bin duz yaziyi yayinlar */
static void bellek_modeli() {
    std::vector<long> veri(1000);
    std::atomic<bool> hazir{false};
    int esgudum = 0;
    std::thread uretici([&] {
        for (int i = 0; i < 1000; ++i) veri[i] = i;
        hazir.store(true, std::memory_order_release);
        ++esgudum;
    });
    while (!hazir.load(std::memory_order_acquire)) std::this_thread::yield();
    long toplam = 0;
    for (long x : veri) toplam += x;
    uretici.join();
    raporla("release/acquire yayini", Bedel::bekleme,
            say("esgudum", esgudum) + " " + say("toplam", toplam));
}

/* 9. Onbellek dostu duzen: ayni alanlar yan yana mi duruyor */
static int atlama(const float* const* erisim, int n) {
    int sayi = 0;
    for (int i = 1; i < n; ++i)
        if (erisim[i] != erisim[i - 1] + 1) ++sayi;
    return sayi;
}
static void veri_duzeni() {
    float aos[12] = {};  // x y z x y z ...
    float soa[12] = {};  // x x x x y y y y z z z z
    const float* aos_x[4];
    const float* soa_x[4];
    for (int i = 0; i < 4; ++i) {
        aos_x[i] = &aos[i * 3];
        soa_x[i] = &soa[i];
    }
    raporla("yalniz x alani", Bedel::bellek,
            "aos=" + std::to_string(atlama(aos_x, 4)) + "/3 soa=" +
                std::to_string(atlama(soa_x, 4)) + "/3");
}

/* 10. Profilleme: aracli sayaclar sicak noktayi gosterir */
static void profil() {
    long hazirla = 0, ara = 0;
    std::vector<int> v;
    for (int i = 0; i < 100; ++i) {
        v.push_back(i * 7 % 50);
        ++hazirla;
    }
    int ikiz = 0;
    for (int i = 0; i < 100; ++i)
        for (int j = i + 1; j < 100; ++j) {
            ++ara;
            if (v[i] == v[j]) ++ikiz;
        }
    raporla("aracli profil", Bedel::kosum,
            say("hazirla", hazirla) + " " + say("ara", ara) + " " + say("ikiz", ikiz));
}

/* 11. Derleme suresi: gizleme deyimi her nesnede bir ayirma yapar */
static int ayirma = 0;
class Pencere {
public:
    explicit Pencere(int no);
    ~Pencere();
    int no() const;

private:
    struct Ic;
    std::unique_ptr<Ic> ic_;
};
struct Pencere::Ic {
    int no;
    explicit Ic(int n) : no(n) { ++ayirma; }
};
Pencere::Pencere(int no) : ic_(std::make_unique<Ic>(no)) {}
Pencere::~Pencere() = default;
int Pencere::no() const { return ic_->no; }
static void gizleme() {
    Pencere a(1), b(2), c(3);
    raporla("gizleme deyimi", Bedel::kosum,
            say("nesne", (a.no() > 0) + (b.no() > 0) + (c.no() > 0)) + " " + say("ayirma", ayirma));
}

/* 12. Paket yonetimi ve derleme araclari: degisen baslik kac birimi yeniden derletir */
struct Birim {
    const char* ad;
    std::vector<std::string> basliklar;
};
static int yeniden(const std::vector<Birim>& birimler, const std::string& degisen) {
    int sayi = 0;
    for (const auto& b : birimler)
        for (const auto& h : b.basliklar)
            if (h == degisen) {
                ++sayi;
                break;
            }
    return sayi;
}
static void derleme_araci() {
    std::vector<Birim> birimler = {
        {"giris.cpp", {"ortak.hpp"}},
        {"hesap.cpp", {"ortak.hpp"}},
        {"rapor.cpp", {"ortak.hpp", "ozel.hpp"}},
    };
    raporla("degisen baslik", Bedel::derleme,
            say("ortak", yeniden(birimler, "ortak.hpp")) + " " +
                say("ozel", yeniden(birimler, "ozel.hpp")));
}

int main() {
    sablon();
    ozellestirme();
    degisken();
    tip_ozelligi();
    sfinae();
    statik();
    kilitler();
    bellek_modeli();
    veri_duzeni();
    profil();
    gizleme();
    derleme_araci();
    ozet();
}
```

`tasindi.cpp` on iki düzeneği kurar; bu dersin düzeneği en üsttedir, ötekiler sonraki
derslerde açılır. Üç dosya birlikte derlenip koşturulunca şu çıktı gelir.

```text
sablon somutlastirma       derleme  cagri=5 govde=3
bool ozellestirmesi        bellek   int=8 bool=1
ozyineli paket acma        derleme  toplam=6 ozyineli=4 katlama=1
if constexpr ile secim     derleme  ayni=6 sorgu=0 etiketli=6
void_t ile algilama        derleme  int=evet string=hayir
crtp ile gonderim          derleme  toplam=35 govde=2
kilitli sayac              bekleme  alma=40000 deger=40000
release/acquire yayini     bekleme  esgudum=1 toplam=499500
yalniz x alani             bellek   aos=3/3 soa=0/3
aracli profil              kosum    hazirla=100 ara=4950 ikiz=50
gizleme deyimi             kosum    nesne=3 ayirma=3
degisen baslik             derleme  ortak=3 ozel=1
siniflar: derleme 6, kosum 2, bellek 2, bekleme 2
derlemede odenen 6, kosumda odenen 6
```

**MP1.** On iki düzeneğin altısı bedeli **derleme** sınıfına, ikisi **koşum**, ikisi
**bellek**, ikisi **bekleme** sınıfına taşıdı; derlemede ödenen altı, koşumda ödenen
altıdır. Çekirdek yalnız bu derste bütün olarak görünür;
sonraki derslerde yalnız ilgili kesiti alınır ve her ders kendi ölçümünü sade C++ ile
kurar: yerel `static` bir sayaç, bir adım sayacı ya da işaretçi eşitliği.

#### İşlev Şablonu ve Şablon Argüman Çıkarımı

`kare` bir **işlev şablonudur**: `T` bir şablon parametresidir ve gövde `T` bilinmeden
yazılır. Çağrı sırasında derleyici argümanın tipine bakıp `T`yi çıkarır; buna **şablon
argüman çıkarımı** denir. Çıkarılan tip için gövde daha önce üretilmediyse derleyici onu üretir; bu üretime
**somutlaştırma** denir. Kayıt, gövdenin içindeki yerel `static` değişkenle tutulur: her
somutlaştırmanın kendi `static`'i vardır ve ilk çağrıda bir kez artar.

```cpp
// kare.hpp — baslikta tanimlanan islev sablonu, govdeleri sayar
#ifndef KARE_HPP
#define KARE_HPP

inline int kare_govde = 0;

template <typename T>
T kare(T x) {
    static const bool kayit = (++kare_govde, true);
    (void)kayit;
    return x * x;
}

#endif
```

Argümanı derleyiciye bırakmak zorunda değiliz: `kare<long>(3)` ile `T` açıkça verilir,
çıkarım yapılmaz ve argüman `long`a dönüştürülür. Aynı fikir bir **sınıf şablonunda** da
geçerlidir.

```cpp
// kutu.hpp — sinif sablonu: her T icin ayri bir sinif
#ifndef KUTU_HPP
#define KUTU_HPP

inline int kutu_govde = 0;

template <typename T>
class Kutu {
public:
    explicit Kutu(T deger) : deger_(deger) {}
    T al() const {
        static const bool kayit = (++kutu_govde, true);
        (void)kayit;
        return deger_;
    }

private:
    T deger_;
};

#endif
```

```cpp
// somutlastirma.cpp — cikarim, acik argumanlar ve sinif sablonu govdeleri
#include <cstdio>

#include "kare.hpp"
#include "kutu.hpp"

int main() {
    int a = kare(2);              // T = int cikarildi
    double b = kare(1.5);         // T = double cikarildi
    long c = kare<long>(3);       // T acikca long verildi
    double d = kare<double>(3);   // 3 double'a donusur, double govdesi yeniden kullanilir

    Kutu<int> iki(2);
    Kutu<int> uc(3);
    Kutu<double> yarim(0.5);

    std::printf("kare %d %.2f %ld %.1f\n", a, b, c, d);
    std::printf("kare govde %d\n", kare_govde);
    std::printf("kutu %d %d %.1f\n", iki.al(), uc.al(), yarim.al());
    std::printf("kutu govde %d\n", kutu_govde);
    return 0;
}
```

```text
kare 4 2.25 9 9.0
kare govde 3
kutu 2 3 0.5
kutu govde 2
```

**MP2.** `kare<double>(3)` yeni gövde doğurmadı, çünkü `double` gövdesi `kare(1.5)` ile zaten vardı.
`Kutu<int>` ile `Kutu<double>` iki ayrı sınıftır; iki `Kutu<int>` nesnesi tek `al` gövdesini
paylaştı. Sınıf şablonunda yalnız **kullanılan** üye işlevler somutlaştırılır.

**MP3.** Gövde, çağrı sayısına göre değil **kullanılan tip sayısına** göre üretilir: dört `kare`
çağrısı üç, üç `Kutu::al` çağrısı iki gövde doğurdu. Bedel **derleme** sınıfına taşındı;
koşumda tip kararı kalmadı, ödenen şey her yeni tip için derleyicinin yazdığı ek koddur.

#### Şablon Neden Başlıkta Tanımlanır

İki şablonun da tanımı `.hpp` içinde, bildirimle birlikte duruyor.

**MP4.** Somutlaştırma,
şablonun **tanımının** çağrının bulunduğu çeviri biriminde görünür olmasını gerektirir:
derleyici `kare(2)` için `int` gövdesini ancak tanımı o birimde görürse üretebilir. Tanım yalnız bir `.cpp` dosyasında
dursa, başka birimden gelen çağrı onu göremez ve gövde üretilemez. Bu yüzden tanım, onu
kullanan her çeviri biriminde görünen başlığa konur. Bunun da bir bedeli var; kursun son
dersleri ona döner: başlığı içeren her birim yeniden derlenir.

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

Gövde sayısını çağrı sayısı ya da argümanın yazılışı değil, **çıkarılan tip** belirler.
Bunu `const` taşıyan bir argümanla sınıyoruz.

```cpp
// sabit-arguman.cpp — const int argumani yeni govde dogurmaz
#include <cstdio>

#include "kare.hpp"

int main() {
    const int sabit = 4;

    int a = kare(sabit);        // T = int, const cikarimda dusur
    int b = kare<int>(sabit);   // T acikca int
    int c = kare(2);            // T = int

    std::printf("kare %d %d %d\n", a, b, c);
    std::printf("govde %d\n", kare_govde);
    return 0;
}
```

```text
kare 16 16 4
govde 1
```

**MP5.** `kare(sabit)` çağrısında argümanın `const`'u şablon argüman çıkarımı sırasında
düşer ve `T` `int` olur; çağrı, `kare<int>` gövdesini kullanır ve yeni gövde doğurmaz.
Üç çağrı, tek gövde; çekirdekteki `cagri=5 govde=3` satırı bu yüzden beş çağrıya üç gövde
der. Sınıf **derleme** kalır, çünkü sayılan şey derlemenin bıraktığı gövdedir.

#### Özet

- Bu dersin ölçtüğü düzenek bedeli **derleme** sınıfına taşır: şablon, kullanılan her yeni
  tip için derleyiciye ayrı bir gövde yazdırır.
- Dört sınıf vardır: **derleme**, **koşum**, **bellek**, **bekleme**; çekirdekte altı
  düzenek derlemede, altısı koşumda ödenir.
- Şablon argüman çıkarımı `T`yi argümandan bulur; açık şablon argümanı çıkarımı atlar.
- Gövde sayısını çıkarılan tip belirler: `const int` argümanı `const`'unu bırakır ve
  yeni gövde doğurmaz.
- Sınıf şablonunda yalnız kullanılan üyeler somutlaştırılır; şablonun tanımı, onu
  kullanan her çeviri biriminde görünür olmalıdır.

#### Sonraki Adım

Bu derste şablon her yeni tip için bir gövde üretti ve bedel derlemeye taşındı. Sıradaki
ders, genel şablonu belirli bir tip için elle değiştirmenin bedeli nereye koyduğunu
soruyor: `Paket<bool>` sekiz değeri tek yuvaya sığdırınca bedel belleğin düzenine, oradan
da koşuma geçiyor.

### Şablon Özelleştirme

Source: https://academia.sh/tr/kurslar/cpp-sablonlar/sablon-ozellestirme

Tam ve kısmi özelleştirme, genel şablon, en özel olanın seçilmesi, bool paketinin bellek düzeni ve bitten okumanın koşuma bıraktığı bedel.

Önceki ders, bir şablonun her yeni tip için yeni bir gövde yazdırdığını ve bu bedelin
**derleme** sınıfında kaldığını gösterdi. Şablonun ürettiği gövde her tip için aynı
kalıptır; ama bazı tipler için kalıp yanlış ya da savurgan olabilir. Bu ders o durumda
kalıbın elle değiştirilmesini, yani **özelleştirmeyi** ele alıyor. Soru değişmedi: bu
düzenek bedeli nereye taşıdı? Burada yanıt bir gövdenin sayısı değil, verinin bellekte kaç
yuvaya yayıldığıdır.

#### Genel Şablon ve Tam Özelleştirme

Sekiz değer tutan bir `Paket<T>` düşünelim. **Genel şablon** her değer için bir yuva
ayırır: `T yuva[8]`. `bool` için bu israftır, çünkü bir değer tek bit taşır ama bir bayt
tutar. **Tam özelleştirme** (`template <>`) belirli bir tip için genel şablonu tümüyle
değiştirir; burada `Paket<bool>` sekiz değeri tek bir `unsigned char` içinde tutar. Arayüz
aynı kalır, gövde değişir.

```cpp
// paket.hpp — genel sablon ve bool icin tam ozellestirme
#ifndef PAKET_HPP
#define PAKET_HPP

template <typename T>
struct Paket {
    T yuva[8] = {};
    const void* yer(int i) const { return &yuva[i]; }
    void yaz(int i, T deger) { yuva[i] = deger; }
    T oku(int i, int&) const { return yuva[i]; }
};

template <>
struct Paket<bool> {
    unsigned char bitler = 0;
    const void* yer(int) const { return &bitler; }
    void yaz(int i, bool deger) {
        if (deger) bitler = static_cast<unsigned char>(bitler | (1u << i));
        else bitler = static_cast<unsigned char>(bitler & ~(1u << i));
    }
    bool oku(int i, int& adim) const {
        adim += 2;   // bir kaydirma, bir maske
        return ((bitler >> i) & 1) != 0;
    }
};

#endif
```

`yer` bir değerin hangi yuvada durduğunu gösterir; yuvaları **işaretçi eşitliğiyle**
sayacağız, adres basmadan. İki işaretçi eşitse iki değer aynı yuvadadır. Aşağıdaki
program her iki paketi aynı kalıpla doldurur, okur ve ayrı yuva sayısını yazar.

```cpp
// ozellestirme.cpp — ayni arayuz, farkli yuva sayisi
#include <cstdio>

#include "paket.hpp"

template <typename T>
static int ayri_yuva(const Paket<T>& p) {
    int ayri = 0;
    for (int i = 0; i < 8; ++i) {
        bool yeni = true;
        for (int j = 0; j < i; ++j)
            if (p.yer(i) == p.yer(j)) yeni = false;
        if (yeni) ++ayri;
    }
    return ayri;
}

template <typename T>
static void dene(const char* ad, Paket<T>& p) {
    int adim = 0;
    p.yaz(1, true);
    p.yaz(6, true);
    std::printf("%s ", ad);
    for (int i = 0; i < 8; ++i) std::printf("%d", static_cast<int>(p.oku(i, adim)));
    std::printf(" yuva=%d\n", ayri_yuva(p));
}

int main() {
    Paket<int> genel;
    Paket<bool> bitli;
    dene("int ", genel);
    dene("bool", bitli);
    return 0;
}
```

```text
int  01000010 yuva=8
bool 01000010 yuva=1
```

**MP6.** İki paket aynı değerleri aynı sırayla okur; fark yalnız yuva sayısındadır:
`Paket<int>` sekiz ayrı yuva, `Paket<bool>` tek yuva kullanır. Çekirdekteki
`bool ozellestirmesi` satırının `int=8 bool=1` sayıları bu ölçümdür. Bedel **bellek**
sınıfına düşer: aynı iş, verinin yerleşimine göre farklı sayıda depolama yuvasına dokunur.

#### Kısmi Özelleştirme ve En Özel Olanın Seçilmesi

Tam özelleştirme tipleri tek tek adlandırır. **Kısmi özelleştirme** ise parametrelerin bir
desenini yakalar: "iki parametre aynı tipse" ya da "tip bir işaretçiyse" gibi. İki
parametreli bir `Cift<T, U>` için genel şablon ayrı tipleri, `Cift<T, T>` kısmi
özelleştirmesi aynı tipleri, `Cift<int, int>` tam özelleştirmesi ise yalnız `int` çiftini
yakalar. Bir kullanımda birden çok özelleştirme uyuyorsa derleyici **en özel olanı** seçer.

```cpp
// cift.hpp — genel sablon, kismi ve tam ozellestirme
#ifndef CIFT_HPP
#define CIFT_HPP

template <typename T, typename U>
struct Cift {
    static const char* tur() { return "farkli"; }
};

template <typename T>
struct Cift<T, T> {
    static const char* tur() { return "ayni"; }
};

template <>
struct Cift<int, int> {
    static const char* tur() { return "tamsayi"; }
};

#endif
```

```cpp
// cift-sec.cpp — hangi ozellestirme secildi
#include <cstdio>

#include "cift.hpp"

int main() {
    std::printf("int,double    %s\n", Cift<int, double>::tur());
    std::printf("double,double %s\n", Cift<double, double>::tur());
    std::printf("int,int       %s\n", Cift<int, int>::tur());
    return 0;
}
```

```text
int,double    farkli
double,double ayni
int,int       tamsayi
```

**MP7.** `Cift<int, int>` hem kısmi desene (`T, T`) hem tam özelleştirmeye uyar; derleyici
tam olanı seçer. `Cift<double, double>` yalnız kısmi desene uyar. Seçim derlemede biter ve
koşumda karar adımı bırakmaz. Bir kural daha var: özelleştirme, o tipin ilk somutlaştırmasından
**önce** görünür olmalıdır; tip zaten genel şablondan üretildikten sonra gelen bir özelleştirme
programı hatalı kılar ve derleyicinin bunu bildirmesi gerekmez. İşlev şablonları ise kısmi özelleştirilemez; aynı iş için
aşırı yükleme kullanılır; bunu Aşırı Yükleme Çözümlemesi dersi işler.

#### İşaretçi Deseni ve Özelleştirmenin Sınırı

Kısmi özelleştirme bir tipin biçimini de yakalayabilir. `Ac<T*>`, `T` ne olursa olsun
işaretçi olan her tipi yakalar; genel şablon ise değeri olduğu gibi döndürür. Burada bir
sınır görünür: bir özelleştirme **genel şablonun üyelerini devralmaz**. Özelleştirme
kendi başına yeni bir sınıf tanımıdır; genel şablonda bulunan ve çağrılmasını beklediğimiz
her üyeyi onda yeniden yazmak gerekir. Arayüzün ortak kalması bir derleyici garantisi
değil, yazarın üstlendiği bir sözleşmedir. `Paket<bool>`'un `yer`, `yaz` ve `oku`
üyelerini genel şablonla aynı adlarla taşıması bu sözleşmenin uygulanmasıdır.

```cpp
// isaretci.hpp — kismi ozellestirme isaretci desenini yakalar
#ifndef ISARETCI_HPP
#define ISARETCI_HPP

template <typename T>
struct Ac {
    static const char* ad() { return "deger"; }
    static T al(const T& x) { return x; }
};

template <typename T>
struct Ac<T*> {
    static const char* ad() { return "isaretci"; }
    static T al(T* const& p) { return *p; }
};

#endif
```

```cpp
// isaretci-sec.cpp — ayni cagri, iki ayri govde
#include <cstdio>

#include "isaretci.hpp"

int main() {
    int x = 7;
    int* p = &x;
    std::printf("%s %d\n", Ac<int>::ad(), Ac<int>::al(x));
    std::printf("%s %d\n", Ac<int*>::ad(), Ac<int*>::al(p));
    return 0;
}
```

```text
deger 7
isaretci 7
```

**MP8.** `Ac<int*>` genel şablondan değil, işaretçi desenini yakalayan kısmi
özelleştirmeden gelir ve değeri işaretçinin gösterdiği yerden okur. İki kullanım da yedi
yazdırır, ama arkalarındaki gövdeler farklıdır; hangisinin kullanılacağı derlemede
belirlenir ve koşumda sorgu kalmaz. Bu dersin sınıfı yine de **bellek** olarak kalır:
bellek ölçümü yalnız `Paket<bool>` satırıdır ve bu bölüm, özelleştirmenin sınırını
gösteren yardımcı bir örnektir.

#### İşlev Şablonunun Tam Özelleştirmesi

İşlev şablonları kısmi özelleştirilemez, ama **tam** özelleştirilebilir. Burada başlıkla
ilgili bir tuzak var: tam özelleştirme artık bir şablon değil, sıradan bir işlevdir, bu
yüzden başlıkta tanımlanıyorsa `inline` olmalıdır; aksi halde başlığı içeren her birim
aynı işlevi tanımlar ve bağlayıcı çift tanım görür. Aşağıdaki `etiket` genel şablonu her
tipe `diger` der, `int` için tam özelleştirme `tamsayi` der.

```cpp
// etiket.hpp — islev sablonu ve inline tam ozellestirme
#ifndef ETIKET_HPP
#define ETIKET_HPP

template <typename T>
const char* etiket() { return "diger"; }

template <>
inline const char* etiket<int>() { return "tamsayi"; }

#endif
```

```cpp
// etiket-sec.cpp — hangi etiket secildi
#include <cstdio>

#include "etiket.hpp"

int main() {
    std::printf("%s %s %s\n", etiket<double>(), etiket<int>(), etiket<long>());
    return 0;
}
```

```text
diger tamsayi diger
```

**MP9.** `etiket<int>` özelleştirmeye, `etiket<double>` ve `etiket<long>` genel şablona
gider; genel şablondan iki ayrı gövde, özelleştirmeden bir gövde doğar. Burada da seçim
derlemede biter. İşlev şablonlarında özelleştirme yerine çoğu zaman aşırı yükleme tercih
edilir, çünkü aşırı yükleme çözümlemesi özelleştirmeden farklı kurallarla aday seçer;
farkı Aşırı Yükleme Çözümlemesi dersi gösterir.

#### `std::vector<bool>` ve Vekil Nesne

Standart kütüphane bu fikri kendisi kullanır: `std::vector<bool>` bir özelleştirmedir ve
standart onun için değerleri bitlere sıkıştıran, yer kazandıran bir gösterim önerir; yaygın
gerçeklemeler bunu `Paket<bool>` gibi yapar. Sonucun bir bedeli var: bir elemanın
**adresini almak** mümkün değildir, çünkü eleman ayrı bir `bool` nesnesi değildir. Bu yüzden
`operator[]` elemanın kendisini değil, o biti okuyup yazan bir **vekil nesne** döndürür.
Elemanın adresini almayı denemek koşturulmaz; tanımlı karşılığı vekil nesneyle çalışmaktır.

```cpp
// vekil.cpp — vector<bool> elemanina vekil nesne uzerinden erisim
#include <cstdio>
#include <vector>

int main() {
    std::vector<bool> v(8, false);
    v[3] = true;      // operator[] bir vekil nesne dondurur, atama biti yazar
    bool uc = v[3];   // vekil nesne bool'a donusur

    int dolu = 0;
    for (std::size_t i = 0; i < v.size(); ++i) dolu += v[i];
    std::printf("dolu %d, v[3] %d\n", dolu, uc ? 1 : 0);
    return 0;
}
```

```text
dolu 1, v[3] 1
```

Bitlerin sıkıştırılması bellekten kazanılan bedeli bir yere geri öder. Nereye, bir sonraki
bölümde ölçülüyor.

#### En Özel Özelleştirme Kazanır

Aynı tip için birden çok kısmi özelleştirme eşleşebilir. Derleyici, kalıbı diğerlerinden
daha belirli olanı seçer: `T*` kalıbı `const T*` kalıbından daha geneldir, çünkü `const
int*` ikisine de uyar ve ikinci kalıp birincinin bir alt kümesidir. Hiçbiri diğerinden daha
belirli değilse kullanım belirsizdir ve derleme durur; bunu düzyazıda bırakıyorum, çünkü
derlenmeyen kod blok olarak yazılmaz. Tam özelleştirme her zaman kısmi olandan önce gelir.

```cpp
// en-ozel.cpp — en belirli kalip secilir
#include <cstdio>

template <typename T>
struct Tur { static const char* ad() { return "genel"; } };

template <typename T>
struct Tur<T*> { static const char* ad() { return "isaretci"; } };

template <typename T>
struct Tur<const T*> { static const char* ad() { return "sabite isaretci"; } };

template <>
struct Tur<int*> { static const char* ad() { return "int isaretci"; } };

int main() {
    std::printf("int          %s\n", Tur<int>::ad());
    std::printf("double*      %s\n", Tur<double*>::ad());
    std::printf("const char*  %s\n", Tur<const char*>::ad());
    std::printf("int*         %s\n", Tur<int*>::ad());
    return 0;
}
```

```text
int          genel
double*      isaretci
const char*  sabite isaretci
int*         int isaretci
```

**MP10.** `int` hiçbir kalıba uymaz ve genel şablon kalır. `double*` yalnız `T*` kalıbına
uyar. `const char*` hem `T*` hem `const T*` kalıbına uyar ve ikincisi daha belirlidir.
`int*` için tam özelleştirme vardır ve kısmi olanı yener. Bu seçim derlemede biter: her
kullanılan tip için bir sınıf tanımı seçilir ve koşumda sorgu bırakmaz.

#### İşlev Şablonları Kısmen Özelleştirilemez

Kısmi özelleştirme yalnız sınıf şablonlarında vardır; işlev şablonunun yalnız tam
özelleştirmesi yazılabilir. İşlev tarafında kısmi eşdeğeri aşırı yüklemedir: `T` ve `T*`
için iki ayrı işlev şablonu yazılır ve aşırı yükleme çözümlemesi hangisinin daha belirli
olduğuna karar verir. Bu karar bir sonraki derslerin konusudur; burada yalnız sonucu
görmek yeter. İşlev şablonlarının tam özelleştirmesi ise çoğunlukla aşırı yüklemeyle
değiştirilir, çünkü özelleştirmeler aşırı yükleme çözümlemesine katılmaz: derleyici önce
genel şablonlar arasından seçer, ardından seçilen şablonun özelleştirmesini kullanır.

```cpp
// islev-yukleme.cpp — islev sablonunda kismi yerine yukleme
#include <cstdio>

template <typename T>
const char* tanit(T) { return "deger"; }

template <typename T>
const char* tanit(T*) { return "isaretci"; }

int main() {
    int n = 5;
    std::printf("tanit(n)  %s\n", tanit(n));
    std::printf("tanit(&n) %s\n", tanit(&n));
    return 0;
}
```

```text
tanit(n)  deger
tanit(&n) isaretci
```

**MP11.** `tanit(n)` için yalnız `T` şablonu uygundur. `tanit(&n)` için ikisi de uygundur ve
`T*` şablonu daha belirlidir, bu yüzden seçilir. Kısmi özelleştirmenin sınıf tarafında
yaptığı iş, işlev tarafında aşırı yükleme çözümlemesinin yaptığı işe döner; her ikisinde
de seçim derlemede biter ve bedel **derleme** sınıfındadır.

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

`Paket<bool>`, sekiz değeri tek yuvaya sığdırarak bellek bedelini düşürdü. Ama bir biti
okumak için önce yuvayı kaydırmak, sonra maskelemek gerekir; `Paket<int>` ise değeri
doğrudan okur. Sayaç, `Paket<bool>::oku` içinde iki adım (kaydırma ve maske) ve
`Paket<int>::oku` içinde sıfır adım sayar. Aşağıdaki program her pakette sekiz okuma yapar.

```cpp
// okuma-adimi.cpp — okuma basina adim
#include <cstdio>

#include "paket.hpp"

template <typename T>
static void say(const char* ad, const Paket<T>& p) {
    int adim = 0;
    int okuma = 0;
    for (int i = 0; i < 8; ++i) {
        p.oku(i, adim);
        ++okuma;
    }
    std::printf("%s okuma=%d adim=%d okuma_basina=%d\n", ad, okuma, adim, adim / okuma);
}

int main() {
    Paket<int> genel;
    Paket<bool> bitli;
    say("int ", genel);
    say("bool", bitli);
    return 0;
}
```

```text
int  okuma=8 adim=0 okuma_basina=0
bool okuma=8 adim=16 okuma_basina=2
```

**MP12.** `Paket<bool>` her okumada iki adım öder, `Paket<int>` sıfır. Bellekten kazanılan
bedel koşuma döndü: yuva sayısı sekizden bire indi, ama her okuma bir kaydırma ve maske
istiyor. Bu dersin merkezi ölçümü **bellek** sınıfında kalır, çünkü çekirdekteki sayı
yuva sayısıdır; okuma başına adım, bedelin tek yönlü kazanılmadığını gösteren sınırdır.
Adım sayısı burada modeldir: kaydırma ve maske, sayaç satırlarıyla sayılmıştır, ölçülmüş
bir süre değildir.

#### Özet

- Bu dersin ölçtüğü düzenek bedeli **bellek** sınıfına taşır: `Paket<bool>` sekiz değeri
  tek yuvada tutar (`int=8 bool=1`).
- Tam özelleştirme (`template <>`) tek bir tipin gövdesini değiştirir; kısmi özelleştirme
  bir parametre desenini yakalar; birden çok uyum varsa en özel olan seçilir.
- Özelleştirme, tipin ilk somutlaştırmasından önce görünür olmalıdır; işlev şablonları
  kısmi özelleştirilemez, tam özelleştirmesi ise başlıkta `inline` olmalıdır.
- Bir özelleştirme genel şablonun üyelerini devralmaz; `Ac<T*>` gibi bir desen kısmi
  özelleştirmeyle yakalanır ve arayüz sözleşmesini yazar korur.
- `std::vector<bool>` elemanının adresi alınamaz; `operator[]` bir vekil nesne döndürür.
- Bitten okuma her erişimde iki adım ister: bellekten kazanılan bedel koşuma geri döner.

#### Sonraki Adım

Bu derste özelleştirme bedeli yuvaya koydu, okuma başına adım da koşuma geri taşıdı.
Sıradaki ders, şablonun parametre sayısını sabitten değişkene çeviriyor: bir parametre
paketi açılırken derleyicinin kaç gövde ürettiği ölçülüyor.

### Değişken Sayıda Şablon Parametresi

Source: https://academia.sh/tr/kurslar/cpp-sablonlar/degisken-sayida-sablon-parametresi

Parametre paketi, sizeof..., paket açma, özyineli açma ile katlama ifadesi karşılaştırması, std::forward ile iletme ve açma yönteminin derleyiciye kaç gövde yazdırdığı.

Önceki ders özelleştirmeyi ölçtü: `Paket<bool>` bedeli yuvadan çıkarıp koşumdaki bir adıma
koydu. Bu ders şablonun parametre sayısını da serbest bırakıyor. Bir işlev, kaç argüman
verilirse versin, hepsini kabul edebilir mi? **Değişken sayılı şablon** bunu sağlar, ama
açma yöntemi derleyiciye kaç gövde yazdırdığını değiştirir. Dersin sorusu o sayıdır: bir
paketi açmak kaç gövde doğurur?

#### Parametre Paketi ve sizeof...

Üç noktalı `typename... T` bildirimi bir **parametre paketi** tanımlar: sıfır ya da daha
çok tipi tek bir ad altında toplar. İşlev parametresi `T... x` ise buna karşılık gelen
**işlev parametre paketidir**. Paketteki eleman sayısı çalışma zamanında değil derlemede
bilinir ve `sizeof...(T)` ile okunur. Paketi kullanmak için `x...` yazılır; buna **paket
açma** denir: derleyici paketin her elemanını virgülle ayrılmış bir listeye çevirir.

Paketi başka bir işleve iletmek için `std::forward` kullanılır. Bir argümanın sol değer ya
da sağ değer olduğu bilgisi, `A&&` biçimindeki parametrede korunur; `std::forward<A>` o
bilgiyi bozmadan iletir. Aşağıdaki program üç şeyi birlikte gösteriyor.

```cpp
// paket-islemleri.cpp — sizeof..., iletme ve tekli katlama
#include <cstdio>
#include <utility>

template <typename... T>
constexpr int say(T...) { return static_cast<int>(sizeof...(T)); }

template <typename... T>
bool hepsi_pozitif(T... x) { return (... && (x > 0)); }

static long topla3(long a, long b, long c) { return a + b + c; }

template <typename F, typename... A>
long ilet(F f, A&&... a) { return f(std::forward<A>(a)...); }

int main() {
    std::printf("say %d %d %d\n", say(), say(1), say(1, 'a', 2.0));
    std::printf("hepsi %d %d %d\n", hepsi_pozitif(1, 2, 3) ? 1 : 0,
                hepsi_pozitif(1, -2) ? 1 : 0, hepsi_pozitif() ? 1 : 0);
    std::printf("ilet %ld\n", ilet(topla3, 1, 2L, 3));
    return 0;
}
```

```text
say 0 1 3
hepsi 1 0 1
ilet 6
```

**MP13.** `say` boş paketle sıfır, üç argümanla üç verir; sayı derlemede sabittir. `hepsi_pozitif`
bir **katlama ifadesi** kullanır: `(... && (x > 0))`, paketi `&&` işleciyle tek bir
ifadeye katlar. Paket boşken `&&` katlaması `true` verir. `ilet` argümanları hiçbir
kopya eklemeden `topla3`e gönderir; `std::forward` yalnız burada, iletme için gerekli.

#### Özyineli Açma ve Taban Durumu

Paketi elle açmanın klasik yolu üç adımdır: ilk argümanı al, geri
kalanıyla aynı işlevi çağır, paket boşalınca duran bir **taban durumu** yaz. Her çağrı bir
eleman daha eksik bir paketle yapılır, yani her adım **ayrı bir şablon gövdesidir**.

Toplamı iki yolla kuralım. `topla_oz` özyineli açar; `topla_kat` aynı toplamı tek katlama
ifadesiyle verir. Her gövde kendi yerel `static`'ini ilk çağrıda bir kez artırır, yani
sayaç üretilen gövdeleri sayar.

```cpp
// toplam.hpp — ozyineli ve katlamali toplam, govdeleri sayar
#ifndef TOPLAM_HPP
#define TOPLAM_HPP

inline int oz_govde = 0;
inline int kat_govde = 0;

inline long topla_oz() {
    static const bool kayit = (++oz_govde, true);
    (void)kayit;
    return 0;
}

template <typename Ilk, typename... Kalan>
long topla_oz(Ilk ilk, Kalan... kalan) {
    static const bool kayit = (++oz_govde, true);
    (void)kayit;
    return ilk + topla_oz(kalan...);
}

template <typename... T>
long topla_kat(T... x) {
    static const bool kayit = (++kat_govde, true);
    (void)kayit;
    return (0L + ... + x);
}

#endif
```

```cpp
// paket-acma.cpp — ayni toplam, iki acma yontemi
#include <cstdio>

#include "toplam.hpp"

int main() {
    long a = topla_oz(1, 2, 3);
    long b = topla_kat(1, 2, 3);
    std::printf("toplam %ld %ld\n", a, b);
    std::printf("ozyineli %d katlama %d\n", oz_govde, kat_govde);
    return 0;
}
```

```text
toplam 6 6
ozyineli 4 katlama 1
```

**MP14.** İki yöntem de altı verir, ama gövde sayıları ayrıdır. `topla_oz(1, 2, 3)` üç
şablon gövdesi (`<int,int,int>`, `<int,int>`, `<int>`) ve bir taban işlevi kullanır: dört.
`topla_kat(1, 2, 3)` aynı toplamı tek gövdeyle verir. Çekirdekteki `ozyineli paket acma`
satırının `ozyineli=4 katlama=1` sayıları bu ölçümdür. Bedel **derleme** sınıfına düşer:
koşumda karar adımı kalmaz, derleyici her açma biçimi için ayrı sayıda kod yazar.

#### Katlama İfadesinin Biçimleri

Katlama ifadesi dört biçimde yazılır. **Tekli** katlama `(... + x)` ya da `(x + ...)`
yalnız paketi taşır; **ikili** katlama `(0L + ... + x)` bir başlangıç değeri ekler. Sol
katlama `((x1 + x2) + x3)` gibi, sağ katlama `(x1 + (x2 + x3))` gibi gruplar. Başlangıç
değeri taşımayan tekli katlama, boş paket için yalnız `&&`, `||` ve `,` işleçlerinde
geçerlidir: `(... && ...)` boş pakette `true`, `(... || ...)` `false`, virgül `void()`
verir. `+` ile tekli katlama boş pakette derlenmez; `topla_kat()` çağrısı için `0L`
başlangıç değeri bu yüzden gerekli. Katlama ifadesi her somutlaştırmada tek gövdeyle
çalışır, çünkü paket açma bir özyineleme değil, derleyicinin ürettiği tek bir ifadedir.

#### Açma Kalıbı: Üç Noktanın Sağ Tarafı

Üç noktanın anlamı sağında durduğu ifadeye bağlıdır. `x...` yalnız `x`'i açar; ama
`(x * 2)...` kalıbın tamamını her eleman için bir kez yazar. Kalıp içinde birden çok paket
varsa hepsi aynı uzunlukta olmalıdır ve eleman eleman birlikte açılır. Açma yeri de
önemlidir: işlev çağrısı argüman listesinde, küme başlatıcıda, şablon argüman listesinde ya
da bir katlama ifadesinde açılabilir. Aşağıdaki program bunlardan ikisini, küme başlatıcıyı ve
katlama ifadesini gösteriyor.

```cpp
// kalip.cpp — kalip acma ve ayni tip sorgusu
#include <cstdio>
#include <type_traits>

template <typename... T>
int ikilisi(T... x) {
    int d[] = {static_cast<int>(x * 2)..., 0};
    int t = 0;
    for (int v : d) {
        t += v;
    }
    return t;
}

template <typename Ilk, typename... T>
constexpr bool hepsi_ayni_v = (std::is_same_v<Ilk, T> && ...);

template <typename... T>
void yaz(T... x) {
    int sira = 0;
    (std::printf("%s%d", sira++ ? "," : "", static_cast<int>(x)), ...);
    std::printf("\n");
}

int main() {
    std::printf("ikilisi %d\n", ikilisi(1, 2, 3));
    std::printf("ayni %d %d\n", hepsi_ayni_v<int, int, int>,
                hepsi_ayni_v<int, int, long>);
    yaz(7, 8L, 'a');
    return 0;
}
```

```text
ikilisi 12
ayni 1 0
7,8,97
```

**MP15.** `ikilisi(1, 2, 3)` kalıbı üç kez yazar: `{2, 4, 6, 0}` kümesi toplamda on iki verir.
Sondaki sıfır, boş pakette `{}` yerine geçerli bir küme sağlar. `hepsi_ayni_v` bir tip
sorgusudur ve katlama ifadesi `&&` ile tüm eşleşmeleri toplar. `yaz` virgül işleciyle
katlanır; virgül katlamasında elemanlar soldan sağa değerlendirilir, bu standardın
garantisidir ve çıktıdaki sıra bunu gösterir (97, `a` karakterinin kodudur).

#### İleten Yapıcı: Paket ve std::forward

Paketin en sık görülen kullanımı, bir nesneyi kurarken argümanları olduğu gibi yapıcıya
iletmektir. `A&&...` biçimindeki parametreler **iletme referansı** paketi oluşturur ve
`std::forward<A>(a)...` her elemanın sol değer ya da sağ değer olduğunu korur. Bunu
görmek için kopyalanıp taşınmasını saydığımız bir tip yazalım.

```cpp
// ileten.cpp — ileten yapici, kopya ve tasima sayisi
#include <cstdio>
#include <utility>

struct Iz {
    static inline int kopya = 0;
    static inline int tasima = 0;
    int deger;
    explicit Iz(int d) : deger(d) {}
    Iz(const Iz& o) : deger(o.deger) { ++kopya; }
    Iz(Iz&& o) noexcept : deger(o.deger) { ++tasima; }
};

struct Sarmal {
    Iz a;
    Iz b;
    template <typename A, typename B>
    Sarmal(A&& x, B&& y) : a(std::forward<A>(x)), b(std::forward<B>(y)) {}
};

template <typename T, typename... A>
T olustur(A&&... a) {
    return T(std::forward<A>(a)...);
}

int main() {
    Iz sol(1);
    Sarmal s1 = olustur<Sarmal>(sol, Iz(2));
    std::printf("kopya=%d tasima=%d\n", Iz::kopya, Iz::tasima);
    Sarmal s2 = olustur<Sarmal>(Iz(3), Iz(4));
    std::printf("kopya=%d tasima=%d\n", Iz::kopya, Iz::tasima);
    std::printf("degerler %d %d %d %d\n", s1.a.deger, s1.b.deger, s2.a.deger, s2.b.deger);
    return 0;
}
```

```text
kopya=1 tasima=1
kopya=1 tasima=3
degerler 1 2 3 4
```

**MP16.** İlk kurulumda `sol` bir sol değerdir, `std::forward<Iz&>` onu kopyalar; geçici
`Iz(2)` bir sağ değerdir ve taşınır. İkinci kurulumda iki geçici de taşınır. `std::forward`
olmasa her argüman adlı bir sol değer olarak yapıcıya gider ve her seferinde kopyalanırdı.
Sayaçlar birikimlidir, ikinci satır ikinci kurulumun iki taşımasını da içerir; bunlar ürün sayısıdır, süre iddiası değildir. Bu iletme zinciri de her argüman tipi
birleşimi için ayrı bir `olustur` gövdesi yazdırır, yani bedel yine **derleme** sınıfındadır.

#### Paket ve Sınıf Şablonu

Paket yalnız işlevlerde değil sınıf şablonlarında da bulunur. `std::tuple<int, double, char>`
bunun örneğidir. Kendi tip listemizi taşıyan boş bir sınıf yazıp eleman sayısını
`sizeof...` ile sorabiliriz; sınıfın kendisi bir değer taşımaz, yalnız tipleri adlandırır.

```cpp
// liste.cpp — tip listesi tasiyan sinif sablonu
#include <cstdio>
#include <type_traits>

template <typename... T>
struct TipListesi {
    static constexpr int boy = static_cast<int>(sizeof...(T));
};

template <typename L>
struct Boy;

template <typename... T>
struct Boy<TipListesi<T...>> {
    static constexpr int deger = sizeof...(T);
};

static_assert(TipListesi<int, char>::boy == 2);
static_assert(Boy<TipListesi<>>::deger == 0);

int main() {
    using L = TipListesi<int, double, char>;
    std::printf("boy %d %d\n", L::boy, Boy<L>::deger);
    std::printf("bos %d\n", TipListesi<>::boy);
    return 0;
}
```

```text
boy 3 3
bos 0
```

**MP17.** `TipListesi<>` boş bir pakettir ve geçerlidir; `sizeof...` sıfır verir. `Boy`
kısmi özelleştirmesi `TipListesi<T...>` kalıbıyla paketi geri toplar: önceki dersin kısmi
özelleştirmesi, bu kez paketle. Hiçbir koşum maliyeti yoktur, çünkü iki `static_assert`
ve üyeler derlemede sabitlenir.

#### Paketi Saklamak: tuple, apply ve index_sequence

Bir paket doğrudan saklanamaz; bir değişkene atanamaz ve bir kapsayıcıya konamaz. Saklamak
için standart kütüphane `std::tuple` verir: farklı tiplerden elemanları tek bir değerde
toplar. `std::apply` bir demetin elemanlarını bir işlevin argümanları olarak açar, yani
demet ile paket arasında köprü kurar. Elemanlara sıra numarasıyla erişmek gerekirse
`std::index_sequence<I...>` derleme zamanında `0, 1, 2, ...` dizisini bir paket olarak
üretir. Bu üç araç özyineleme yazmadan paketin elemanlarını konumlarıyla birlikte işler.

```cpp
// demet.cpp — tuple, apply ve index_sequence
#include <cstdio>
#include <tuple>
#include <type_traits>
#include <utility>

template <typename T, std::size_t... I>
long agirlikli(const T& t, std::index_sequence<I...>) {
    return (((static_cast<long>(I) + 1) * std::get<I>(t)) + ...);
}

template <typename... A>
long agirlikli_toplam(A... a) {
    auto t = std::make_tuple(a...);
    return agirlikli(t, std::index_sequence_for<A...>{});
}

int main() {
    auto t = std::make_tuple(2, 3, 4);
    long duz = std::apply([](auto... e) { return (0L + ... + e); }, t);
    std::printf("apply %ld\n", duz);
    std::printf("agirlikli %ld\n", agirlikli_toplam(2, 3, 4));
    using ikinci = std::tuple_element_t<1, std::tuple<int, double, char>>;
    std::printf("tip %d\n", std::is_same_v<ikinci, double>);
    return 0;
}
```

```text
apply 9
agirlikli 20
tip 1
```

**MP18.** `std::apply` demeti açar ve katlama ifadesi `2 + 3 + 4` toplamını verir. `agirlikli`
her elemanı konum numarasıyla çarpar: `1*2 + 2*3 + 3*4`. `index_sequence_for<A...>` paketle
aynı uzunlukta bir dizi üretir ve `I...` bu diziyle `A...` ile birlikte açılır. Üçüncü
satırda `tuple_element_t<1, ...>` paketten ikinci tipi seçer; `double` olması beklenir
ve sorgu bunu doğrular. Hepsi derlemede çözülür, her farklı eleman sayısı için ayrı bir
gövde yazdırır, yani yine **derleme** sınıfındadır.

Paketin sınırları da var. Boş bir `std::tuple<>` geçerlidir, ama boş pakette `[0]` gibi bir
konum yoktur. Paket açma ancak bir kalıbın içinde yapılabilir; çıplak `x...` tek başına
bir ifade değildir. Bir sınıf şablonunda paket parametresi son
parametre olmalıdır. İşlev şablonunda paketin ardından parametre gelebilir, ama o parametre
çıkarılabilmeli ya da varsayılan bir argüman taşımalıdır; `template <typename... A,
typename B>` gibi bir sıra, `B` çıkarılamıyorsa kullanılamaz. Özyineli açmada paketin bir
ilk elemanla (`Ilk, Kalan...`) birlikte yazılması ise başka bir nedene dayanır: ilk elemanı
ayırıp geri kalanla özyinelemek.

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

Gövde sayısı çağrı sayısına değil, **tip listesine** bağlıdır. Aynı tip listesiyle ikinci
bir çağrı mevcut gövdeleri kullanır; farklı tipli bir çağrı yenilerini üretir.

```cpp
// paket-yeniden.cpp — ayni tip listesi govdeyi yeniden kullanir
#include <cstdio>

#include "toplam.hpp"

int main() {
    topla_oz(1, 2, 3);
    topla_kat(1, 2, 3);
    std::printf("ilk        ozyineli %d katlama %d\n", oz_govde, kat_govde);

    topla_oz(4, 5, 6);
    topla_kat(4, 5, 6);
    std::printf("ikinci     ozyineli %d katlama %d\n", oz_govde, kat_govde);

    topla_oz(1, 2, 3L);
    topla_kat(1, 2, 3L);
    std::printf("yeni tipli ozyineli %d katlama %d\n", oz_govde, kat_govde);
    return 0;
}
```

```text
ilk        ozyineli 4 katlama 1
ikinci     ozyineli 4 katlama 1
yeni tipli ozyineli 7 katlama 2
```

**MP19.** `(4, 5, 6)` çağrıları da `int, int, int` listesidir: gövde sayıları değişmez,
dörtten dörde. `(1, 2, 3L)` ise yeni bir tip listesidir: `<int,int,long>`, `<int,long>` ve
`<long>` üç yeni özyineli gövde doğurur, dörtten yediye çıkar; taban işlevi aynı kalır.
Katlama tarafında da yeni liste bir yeni gövde ekler. Bu yüzden sayı ölçünün kendisi
değildir; bedelin hangi sınıfa yazıldığını gösteren kanıttır: özyineli açma tip çeşitliliği arttıkça yeni gövde
yazdırır, ve bu bedel **derleme** sınıfında kalır.

#### Özet

- Bu dersin ölçtüğü düzenek bedeli **derleme** sınıfına taşır: bir paketi açmak, açma
  yöntemine göre farklı sayıda gövde üretir.
- `typename... T` bir parametre paketidir; `sizeof...(T)` eleman sayısını derlemede verir,
  `x...` paketi açar.
- Özyineli açma her adımda ayrı bir şablon gövdesi ve bir taban durumu ister
  (`ozyineli=4`); katlama ifadesi aynı iş için tek gövde yeter (`katlama=1`).
- Tekli katlama boş paketi yalnız `&&`, `||` ve `,` ile kabul eder; ikili katlama
  başlangıç değeri taşır.
- Aynı tip listesi gövdeyi yeniden kullanır (4 → 4); yeni bir tip listesi yeni gövdeler
  doğurur (4 → 7).

#### Sonraki Adım

Bu derste paket açma, tip listesine göre gövde yazdırdı ve bedel derlemede kaldı.
Sıradaki ders, tipin kendisine soru soran araçları, tip özelliklerini ele alıyor: bir
şablonun içinde `if constexpr` ile yapılan seçim koşumda kaç sorgu bırakıyor?

### Tip Özellikleri

Source: https://academia.sh/tr/kurslar/cpp-sablonlar/tip-ozellikleri

Standart tip özellikleri, if constexpr ile derleme zamanında seçim, static_assert, kendi tip özelliğini yazmak ve seçimin koşumda sıfır sorgu bırakması.

Önceki ders paket açmanın kaç gövde yazdırdığını saydı. Şablonun içinde tipe bakıp davranışı
değiştirmek gerektiğinde ikinci bir soru çıkar: bu bakış ne zaman yapılır ve kim öder?
**Tip özelliği**, bir tip hakkında derleme sırasında yanıt veren küçük bir şablondur. Dersin
iddiası şu: seçim derlemede verildiğinde koşumda sorgu kalmaz. Bedel **derleme** sınıfında
kalır ve yanında ürün sayısını taşır.

Bu ayrım M11/K01'den farklı bir soruya cevap verir. K01 kararı kimin verdiğini sordu; bu
kurs ise bedelin kimin ödediğini ve neyle sayıldığını soruyor. K01'in `derleyici` sınıfı bir
kararı adlandırırdı. Buradaki **derleme** sınıfı bir bedeli adlandırır ve yanında daima
bir ürün sayısı taşır.

#### Sorgular ve Dönüşümler

`<type_traits>` iki tür özellik verir. **Sorgular** bir `bool` üretir: `std::is_integral_v<T>`
tip bir tamsayı tipiyse doğrudur, `std::is_same_v<A, B>` iki tip özdeşse doğrudur.
**Dönüşümler** yeni bir tip üretir: `std::remove_cvref_t<T>` bir tipten `const`, `volatile`
ve referans niteleyicilerini soyar; `std::conditional_t<K, A, B>` koşul doğruysa `A`,
değilse `B` olur. Sonekli `_v` ve `_t` biçimleri, `::value` ve `::type` yazımının kısa
halidir. Hepsi derlemede değerlendirilir.

```cpp
// ozellikler.cpp — sorgular ve donusumler
#include <cstdio>
#include <string>
#include <type_traits>

int main() {
    std::printf("integral %d %d %d\n", std::is_integral_v<int>,
                std::is_integral_v<double>, std::is_integral_v<bool>);
    std::printf("ayni %d %d\n", std::is_same_v<int, int>,
                std::is_same_v<int, long>);
    std::printf("soyma %d %d\n",
                std::is_same_v<std::remove_cvref_t<const int&>, int>,
                std::is_same_v<std::remove_cvref_t<std::string&&>, std::string>);
    using secilen = std::conditional_t<(sizeof(int) >= 2), int, long>;
    std::printf("kosul %d\n", std::is_same_v<secilen, int>);
    return 0;
}
```

```text
integral 1 0 1
ayni 1 0
soyma 1 1
kosul 1
```

`bool` bir tamsayı tipidir, bu yüzden üçüncü sorgu doğrudur. `const int&` soyulunca `int`
olur; referans niteleyicisi (`&&`) de soyulur. `conditional_t` bir değer değil bir tip seçer:
`secilen` burada `int`'e eşittir. Bu çıktı standardın garantisidir; ilk iki sorgunun
sonucu hiçbir uygulamada değişmez.

#### if constexpr ile Seçim

Sorgu sonucunu bir dal seçmek için kullanmanın yolu `if constexpr`'dır. Koşul derlemede
değerlendirilir ve **atılan dal o somutlaştırmada derlenmez**. Sıradan `if` ise iki dalı da
derler; bir dalda `x.size()` yazıp `T` olarak `int` verirseniz, o dal hiç çalışmasa bile
derleme durur. `if constexpr` bu sorunu çözer: `T` tamsayıysa `.size()` dalına derleyici
bakmaz.

Aşağıda `tur` her tipe bir etiket verir. `tur_etiketli` aynı işi bir koşum parametresiyle,
yani çalışırken verilen kararla yapar ve her çağrıda sorgu sayar.

```cpp
// tur.hpp — derleme zamani ve kosum zamani secim
#ifndef TUR_HPP
#define TUR_HPP

#include <string>
#include <type_traits>

inline int sorgu = 0;
inline int etiketli_sorgu = 0;

template <typename T>
const char* tur(T) {
    if constexpr (std::is_integral_v<T>) {
        return "tamsayi";
    } else if constexpr (std::is_floating_point_v<T>) {
        return "kayan";
    } else {
        return "diger";
    }
}

template <typename T>
const char* tur_etiketli(T, int etiket) {
    ++etiketli_sorgu;
    if (etiket == 0) {
        return "tamsayi";
    }
    if (etiket == 1) {
        return "kayan";
    }
    return "diger";
}

template <typename T>
long uzunluk(T x) {
    if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::string>) {
        return static_cast<long>(x.size());
    } else {
        return static_cast<long>(x);
    }
}

#endif
```

```cpp
// tur-sec.cpp — ayni sonuc, farkli sorgu sayisi
#include <cstdio>
#include <string>

#include "tur.hpp"

int main() {
    int ayni = 0;
    ayni += std::string(tur(1)) == tur_etiketli(1, 0);
    ayni += std::string(tur(2L)) == tur_etiketli(2L, 0);
    ayni += std::string(tur('a')) == tur_etiketli('a', 0);
    ayni += std::string(tur(1.5)) == tur_etiketli(1.5, 1);
    ayni += std::string(tur(2.5f)) == tur_etiketli(2.5f, 1);
    ayni += std::string(tur(std::string("x"))) == tur_etiketli(std::string("x"), 2);
    std::printf("ayni=%d sorgu=%d etiketli=%d\n", ayni, sorgu, etiketli_sorgu);
    std::printf("uzunluk %ld %ld\n", uzunluk(std::string("abcd")), uzunluk(7));
    return 0;
}
```

```text
ayni=6 sorgu=0 etiketli=6
uzunluk 4 7
```

**MP20.** Altı çağrının hepsinde iki yöntem aynı etiketi verir (`ayni=6`). `tur` koşumda
hiçbir sorgu yapmaz (`sorgu=0`): hangi dalın seçileceği derleyicide karar verilmiştir ve
her somutlaştırmada tek bir `return` kalır. `tur_etiketli` ise her çağrıda bir etiket
okur (`etiketli=6`). Bu satır, çekirdekteki `if constexpr ile secim` ölçümüdür. `uzunluk`
da atılan dalın neden derlenmediğini gösterir: `int` için `x.size()` dalı yoktur, `string`
için `static_cast<long>(x)` dalı yoktur. Düz `if` ile ikisi de derleme hatası olurdu.

#### static_assert ve Kısıt

`static_assert(koşul, "mesaj")` koşulu derlemede denetler. Koşul yanlışsa derleme durur ve
mesaj derleyici çıktısında görünür; doğruysa kod üretilmez. Burada başarısızlık düzyazıda
anlatılır, çünkü derlenmeyen kod blok olarak yazılmaz: `static_assert(std::is_integral_v<T>)`
içeren bir şablonu `double` ile somutlaştırmak derlemeyi durdurur; derleyiciler genellikle
mesajın yanında somutlaştırmanın nereden istendiğini de gösterir. Böylece yanlış kullanım, koşumdaki bir hata yerine derlemede yakalanır.

#### Kendi Tip Özelliğinizi Yazmak

Standart özellikler `std::true_type` ve `std::false_type` üzerine kuruludur. İkisinin de
`::value` üyesi vardır. Kendi özelliğinizi yazmak için **genel şablon** `false_type`'tan
türer, ilgilendiğiniz tip için **kısmi özelleştirme** `true_type`'tan türer.

```cpp
// ozel-ozellik.cpp — kendi tip ozelligi
#include <cstdio>
#include <string>
#include <type_traits>
#include <vector>

template <typename T>
struct dizi_mi : std::false_type {};

template <typename U>
struct dizi_mi<std::vector<U>> : std::true_type {};

template <typename T>
inline constexpr bool dizi_mi_v = dizi_mi<T>::value;

static_assert(dizi_mi_v<std::vector<int>>, "vector dizidir");
static_assert(!dizi_mi_v<int>, "int dizi degildir");

int main() {
    std::printf("dizi_mi %d %d %d\n", dizi_mi_v<std::vector<double>>,
                dizi_mi_v<std::string>, dizi_mi_v<int>);
    return 0;
}
```

```text
dizi_mi 1 0 0
```

Şablon Özelleştirme dersindeki kısmi özelleştirme burada işe yarıyor: `std::vector<U>` kalıbı eşleşirse
`true_type` seçilir, hiçbiri eşleşmezse genel şablon kalır. O derste görüldüğü gibi
özelleştirmenin ilk kullanımdan önce görünür olması gerekir. İki `static_assert` bu
dosyada hiçbir kod üretmez; program derlenebildiyse ikisi de geçmiştir.

#### Tipler Arası İlişkiler

Bazı sorgular tek bir tipe değil, iki tip arasındaki ilişkiye bakar. `std::is_base_of_v<B, D>`
`D`, `B`'den türemişse doğrudur. `std::is_convertible_v<A, B>` bir `A` değerinin örtük olarak
`B`'ye dönüşüp dönüşemeyeceğini sorar. `std::is_trivially_copyable_v<T>` tipin bellek
kopyasıyla kopyalanabilir olup olmadığını söyler; bir `std::string` içinde sahiplik taşıdığı
için bu soruda `false` verir: kopyası bayt bayt değil, kendi kopya yapıcısıyla yapılır. `std::decay_t<T>` ise bir değerin işleve değer olarak
verilmesindeki tip dönüşümünü yapar: referansı ve `const`'u soyar, dizi
tipini işaretçiye dönüştürür.

```cpp
// iliski.cpp — tip iliskileri ve decay
#include <cstdio>
#include <string>
#include <type_traits>

struct Taban {};
struct Tureyen : Taban {};

int main() {
    std::printf("taban %d %d\n", std::is_base_of_v<Taban, Tureyen>,
                std::is_base_of_v<Tureyen, Taban>);
    std::printf("donusum %d %d\n", std::is_convertible_v<int, double>,
                std::is_convertible_v<std::string, int>);
    std::printf("kopya %d %d\n", std::is_trivially_copyable_v<int>,
                std::is_trivially_copyable_v<std::string>);
    std::printf("decay %d %d\n",
                std::is_same_v<std::decay_t<const int&>, int>,
                std::is_same_v<std::decay_t<int[3]>, int*>);
    return 0;
}
```

```text
taban 1 0
donusum 1 0
kopya 1 0
decay 1 1
```

**MP21.** `is_base_of` yönlüdür: `Tureyen`, `Taban`'dan türer, tersi doğru değildir.
`int`, `double`'a dönüşür; `std::string`, `int`'e dönüşmez. `int` bellek kopyasıyla
kopyalanabilir, `std::string` ise bellek kopyasıyla kopyalanamaz. `decay_t<int[3]>` dizi tipini `int*`'e
indirger. Bu sorguların hepsi derlemede cevaplanır; çıktıdaki satırlar derlemenin
ürettiği cevaplardır, bir süre değil. Hiçbiri koşumda bir sorgu bırakmaz: `int` ve `std::string`
için değerler zaten derleyicinin elindedir.

#### Kendi Dönüşümünüzü Yazmak

Dönüşümler de aynı kalıpla yazılır. Genel şablon tipi olduğu gibi bırakır, kısmi
özelleştirmeler bir kalıbı soyar. Standart `std::remove_pointer_t` bir işaretçi katmanını
soyar; burada bütün katmanları soyan özyineli bir sürümü bir `_t` takma adıyla birlikte
kuruyoruz. `using` ile tanımlanan `::tip` üyesi,
standart kütüphanenin `::type`'ının yerini tutar.

```cpp
// soy.cpp — kendi donusum ozelligi
#include <cstdio>
#include <type_traits>

template <typename T>
struct soy_isaretci {
    using tip = T;
};

template <typename T>
struct soy_isaretci<T*> {
    using tip = typename soy_isaretci<T>::tip;
};

template <typename T>
using soy_isaretci_t = typename soy_isaretci<T>::tip;

static_assert(std::is_same_v<soy_isaretci_t<int>, int>);
static_assert(std::is_same_v<soy_isaretci_t<int*>, int>);

int main() {
    std::printf("soy %d %d %d\n", std::is_same_v<soy_isaretci_t<char**>, char>,
                std::is_same_v<soy_isaretci_t<const int*>, const int>,
                std::is_same_v<soy_isaretci_t<double>, double>);
    return 0;
}
```

```text
soy 1 1 1
```

**MP22.** Kısmi özelleştirme bir işaretçi katmanını soyar ve kendi kendini çağırarak kalan
tipe iner: `char**` iki adımda `char` olur. Bu özyineleme derleyicinin içindedir; her
katman için bir somutlaştırma yazdırır ve koşumda iz bırakmaz. Dikkat edilmesi gereken
bir nokta `const int*` örneğidir: işaretçinin kendisi değil gösterdiği tip `const int`'tir,
bu yüzden sonuç `const int` olarak kalır. İşaretçinin kendisinin `const`'luğu başka bir
konumdadır ve bu kalıp ona bakmaz.

#### Tip Özelliği ile Kısıt Birleştirmek

Tip özellikleri tek tek değil, birlikte de sorulabilir. Bir
işlev yalnız işaretsiz bir tamsayı tipi için çalışsın istersek `static_assert` ile birkaç
özelliği birlikte denetleriz: `std::is_integral_v<T> && std::is_unsigned_v<T>`. Mantık
işleçleriyle birleştirilebilen koşullar `std::conjunction_v`, `std::disjunction_v` ve
`std::negation_v` ile de yazılabilir; bu ikinci yazım kısa devre yapar, yani sonraki
özellikleri koşul belli olunca somutlaştırmaz.

```cpp
// birlestir.cpp — ozellikleri birlestirmek
#include <cstdio>
#include <type_traits>

template <typename T>
constexpr bool isaretsiz_tamsayi_v = std::is_integral_v<T> && std::is_unsigned_v<T>;

template <typename T>
constexpr bool sayisal_degil_v =
    std::conjunction_v<std::negation<std::is_integral<T>>,
                       std::negation<std::is_floating_point<T>>>;

int main() {
    std::printf("isaretsiz %d %d %d\n", isaretsiz_tamsayi_v<unsigned>,
                isaretsiz_tamsayi_v<int>, isaretsiz_tamsayi_v<double>);
    std::printf("sayisal_degil %d %d\n", sayisal_degil_v<int>,
                sayisal_degil_v<const char*>);
    return 0;
}
```

```text
isaretsiz 1 0 0
sayisal_degil 0 1
```

**MP23.** `unsigned` hem tamsayı hem işaretsizdir, `int` işaretlidir, `double` tamsayı
değildir. `sayisal_degil_v` yalnız tamsayı ve kayan nokta olmayan tiplerde doğrudur: `int`
için yanlış, işaretçi için doğru. Burada kayıt altına alınacak şey, bu bileşimlerin de
derleyicide tamamen çözülmesidir; koşumda iz kalmaz, bedel yine **derleme** sınıfındadır.

#### Dönüş Tipi de Seçilir

`if constexpr` dalları farklı tipte değer döndürebilir; atılan dal dönüş tipi çıkarımına
katılmaz. `auto` dönüş tipi bu yüzden her somutlaştırmada tek bir `return` görür. Sıradan
bir `if` ile aynı işlevde `long` ve `double` döndürmek, iki `return` farklı tip
çıkardığı için derleme hatasıdır. Dönüş tipini açıkça yazmak istersek `std::conditional_t`
bir tip adı hesaplar.

```cpp
// donus.cpp — dallara gore donus tipi
#include <cstdio>
#include <type_traits>

template <typename T>
auto ikiyle(T x) {
    if constexpr (std::is_integral_v<T>) {
        return x * 2L;
    } else {
        return x * 2.0;
    }
}

template <typename T>
using genis_t = std::conditional_t<std::is_integral_v<T>, long long, double>;

int main() {
    std::printf("ikiyle %d %d\n",
                std::is_same_v<decltype(ikiyle(1)), long>,
                std::is_same_v<decltype(ikiyle(1.5f)), double>);
    std::printf("genis %d %d\n", std::is_same_v<genis_t<short>, long long>,
                std::is_same_v<genis_t<float>, double>);
    std::printf("deger %ld %.1f\n", ikiyle(21), ikiyle(1.25f));
    return 0;
}
```

```text
ikiyle 1 1
genis 1 1
deger 42 2.5
```

**MP24.** `ikiyle(1)` tamsayı dalını seçer ve `long` döndürür; `ikiyle(1.5f)` diğer dalı seçer
ve `double` döndürür. İki dönüş tipi tek işlevin iki somutlaştırmasındadır, ayrı gövdeleri
vardır. `genis_t` aynı kararı bir tip takma adına taşır. İkinci satırdaki `short` ve
`float` argümanları, tamsayı ve kayan nokta olarak ayrı dallara düşer. Seçim derlemede
bittiği için koşumda dal sorgusu bırakmaz.

Desteklenmeyen tipleri bir `else` dalında `static_assert` ile yasaklamak istiyorsanız,
koşulun `T`'ye bağımlı olması gerekir; bağımlı olmayan bir koşul, o dal hiç seçilmese bile
şablon tanımlanırken değerlendirilebilir ve derlemeyi durdurabilir. Bu yüzden bu tür
dallarda koşulu `T`'ye bağlayan bir tip özelliği kullanın.

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

Derleme zamanında verilen karar koşumda bir adım bırakmaz, ama `constexpr` yazmak bunu
tek başına garanti etmez. `constexpr` bir işlev, hem derlemede hem koşumda çağrılabilir
demektir. Hangisinin olacağını bağlam belirler: bir `constexpr` değişkenin ilk değeri ya da
`static_assert` gibi sabit değerlendirme gerektiren bir bağlam değerlendirmeyi derlemeye
taşır. `std::is_constant_evaluated()` bu iki durumu ayırt eder.

```cpp
// sabit-degerlendirme.cpp — ayni constexpr islev, iki baglam
#include <cstdio>
#include <type_traits>

inline int adim = 0;

constexpr int kareler_toplami(int n) {
    int t = 0;
    for (int i = 1; i <= n; ++i) {
        if (!std::is_constant_evaluated()) {
            ++adim;
        }
        t += i * i;
    }
    return t;
}

int main() {
    constexpr int a = kareler_toplami(10);
    std::printf("derleme  sonuc=%d adim=%d\n", a, adim);

    volatile int n = 10;
    int b = kareler_toplami(n);
    std::printf("kosum    sonuc=%d adim=%d\n", b, adim);
    return 0;
}
```

```text
derleme  sonuc=385 adim=0
kosum    sonuc=385 adim=10
```

**MP25.** Aynı işlev, aynı sonucu verir; `constexpr` değişkene atanan çağrı derlemede
değerlendirilir ve sayaca dokunmaz (`adim=0`). `volatile` bir argüman ise derleyicinin
değeri bilmesini engeller; çağrı koşumda yapılır ve on yineleme sayılır (`adim=10`). Bu
yüzden `constexpr` bir söz değil, bir izindir: bedeli derlemeye taşıyan, sabit değerlendirme
gerektiren bağlamdır. Sayı sınıflandırmanın kanıtıdır; ölçü sorusunun yanıtı bir sınıf
adıdır, ve burada bu ad **derleme** sınıfıdır.

#### Özet

- Bu dersin ölçtüğü düzenek bedeli **derleme** sınıfına taşır: tip sorgusu ve dalın
  seçilmesi derlemede biter, koşumda `sorgu=0` kalır.
- Sorgular (`is_integral_v`, `is_same_v`) `bool` verir; dönüşümler (`remove_cvref_t`,
  `conditional_t`) tip verir.
- `if constexpr` atılan dalı o somutlaştırmada derlemez; düz `if` iki dalı da derler.
- Kendi tip özelliği genel şablonun `false_type`'a, kısmi özelleştirmenin `true_type`'a
  türemesiyle kurulur; `static_assert` yanlış kullanımı derlemede durdurur.
- `constexpr` taşımayı garanti etmez: aynı işlev sabit değerlendirmede `adim=0`, koşumda
  `adim=10` verir.

#### Sonraki Adım

Bu derste tip sorgusu, seçimi derlemeye çekti ve koşumda sıfır sorgu bıraktı. Sıradaki
ders, aynı sorguyu bir adım geriye alıyor: bir aday şablon uygun değilse derleyici onu
hata vermeden nasıl eler, ve aşırı yükleme çözümlemesi bu elemeden sonra neyi seçer?

### Aşırı Yükleme Çözümlemesi ve SFINAE

Source: https://academia.sh/tr/kurslar/cpp-sablonlar/asiri-yukleme-cozumlemesi-ve-sfinae

Aday ve uygun aday kavramı, şablon ile şablon olmayan işlevin yarışı, enable_if_t ile aday eleme, void_t ile algılama ve C++20 kavramlarıyla kısıtlama.

Önceki ders tipe bakıp dal seçmeyi gösterdi: karar bir `if constexpr` içinde veriliyordu.
Bir adım önce bir soru daha var. Aynı adla birkaç işlev varsa derleyici hangisini çağırır
ve uygun olmayan bir şablon aday listesinde ne olur? Bu ders çağrının nasıl seçildiğini ve
şablonun yanlış tiple karşılaştığında hata vermeden nasıl elendiğini ölçüyor. Seçim
derlemede biter ve bedel yine **derleme** sınıfındadır.

#### Aday, Uygun Aday, En İyi Aday

Bir işlev çağrısında derleyici üç aşamadan geçer. Önce çağrılan adla görünen tüm işlevleri
ve işlev şablonlarını **aday** olarak toplar. Sonra argüman sayısı ve dönüşümü uygun
olanları **uygun aday** listesine alır. Son olarak uygun adaylar arasında argümanlara en
iyi uyan **en iyi aday** seçilir. Eşit iyilikte iki aday kalırsa ve hiçbir eşitlik kuralı
birini öne çıkarmazsa çağrı belirsizdir ve derleme durur.

Şablon ile şablon olmayan işlev eşit iyilikte yarışırsa şablon olmayan kazanır. Ama
şablon, argüman tipine tam uyum sağlarken şablon olmayan işlev bir dönüşüm gerektiriyorsa
şablon kazanır, çünkü tam uyum dönüşümden iyidir.

```cpp
// yukleme.cpp — sablon ve sablon olmayan aday
#include <cstdio>
#include <string>

const char* sec(int) { return "int"; }
const char* sec(double) { return "double"; }

template <typename T>
const char* sec(T) { return "sablon"; }

int main() {
    std::printf("sec(1)      %s\n", sec(1));
    std::printf("sec(1.5)    %s\n", sec(1.5));
    std::printf("sec('a')    %s\n", sec('a'));
    std::printf("sec(2.5f)   %s\n", sec(2.5f));
    std::printf("sec(string) %s\n", sec(std::string("x")));
    return 0;
}
```

```text
sec(1)      int
sec(1.5)    double
sec('a')    sablon
sec(2.5f)   sablon
sec(string) sablon
```

`sec(1)` ve `sec(1.5)` için şablon olmayan işlev tam uyar ve eşitlikte kazanır. `'a'` bir
`char`'dır; `int` adayı bir tamsayı terfisi gerektirir, şablon ise `T = char` ile tam uyar,
bu yüzden şablon kazanır. `float` için de aynıdır. `string` için yalnız şablon uygun
adaydır.

#### SFINAE: Yerine Koyma Başarısızlığı Hata Değildir

Şablon argümanı çıkarıldıktan sonra derleyici çıkarılan tipi şablonun bildirimine koyar.
Bu koyma geçersiz bir tip ya da ifade üretirse, o şablon aday listesinden sessizce düşer
ve derleme hata vermez. İlke **SFINAE** adıyla bilinir: yerine koyma başarısızlığı hata
değildir (substitution failure is not an error). Hata ancak hiçbir aday kalmazsa
verilir.

SFINAE'yi kasıtlı kullanmanın klasik yolu `std::enable_if_t<koşul, tip>`'tir: koşul
doğruysa `tip`'e eşittir, yanlışsa yerine koyma başarısız olur ve şablon elenir. Çoğunlukla
tür olmayan bir şablon parametresinin tipine yerleştirilir ve o parametreye `= 0` gibi bir
varsayılan değer verilir.

```cpp
// enable-if.cpp — enable_if_t ile aday eleme
#include <cstdio>
#include <type_traits>

template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
const char* tur(T) { return "tamsayi"; }

template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
const char* tur(T) { return "kayan"; }

int main() {
    std::printf("tur(7)   %s\n", tur(7));
    std::printf("tur(2.5) %s\n", tur(2.5));
    return 0;
}
```

```text
tur(7)   tamsayi
tur(2.5) kayan
```

**MP26.** `tur(7)` için ikinci şablonun bildirimi `enable_if_t<false, int>` olur, yerine
koyma başarısız olur ve ikinci aday elenir; geriye `tamsayi` kalır. `tur(2.5)` için tersi
olur. İki şablonun imzası aynı görünür, ama şablon parametrelerinin tipleri farklı
koşullara bağlı olduğundan imzalar farklıdır ve çakışma olmaz. Seçim derlemededir ve koşumda sorgu
bırakmaz.

#### Dönüşüm Sıralaması

Uygun adaylar arasında "en iyi"nin ne demek olduğu argümanın her adaya hangi dönüşümle
bağlandığına bakar. Dönüşümler iyiden kötüye şöyle sıralanır: tam uyum, tamsayı ya da
kayan nokta terfisi, standart dönüşüm, kullanıcı tanımlı dönüşüm ve en sonda üç noktalı
parametre. Bir aday her argümanda diğerlerinden kötü değilse ve en az bir argümanda daha
iyiyse en iyidir. İki aday birbirini yenemiyorsa çağrı belirsizdir ve derleme durur; bu hatayı
düzyazıda anlatıyorum, çünkü derlenmeyen kod blok olarak yazılmaz.

```cpp
// siralama.cpp — donusum siralamasi
#include <cstdio>

const char* h(int) { return "int"; }
const char* h(long) { return "long"; }
const char* h(double) { return "double"; }

const char* g(long) { return "long"; }
const char* g(...) { return "uc nokta"; }

int main() {
    short s = 1;
    std::printf("h('a')  %s\n", h('a'));
    std::printf("h(s)    %s\n", h(s));
    std::printf("h(1.5f) %s\n", h(1.5f));
    std::printf("h(2L)   %s\n", h(2L));
    std::printf("g(1)    %s\n", g(1));
    std::printf("g(1.5)  %s\n", g(1.5));
    return 0;
}
```

```text
h('a')  int
h(s)    int
h(1.5f) double
h(2L)   long
g(1)    long
g(1.5)  long
```

**MP27.** `char` ve `short` argümanları `int`'e terfiyle bağlanır ve terfi, `long`'a standart
dönüşümden iyidir; bu yüzden ikisi de `int` adayını seçer. `float`, `double`'a terfiyle
bağlanır. `2L` için `long` tam uyumdur. `g(1)` için `long`'a standart dönüşüm ile üç nokta
yarışır: dönüşüm kazanır. `g(1.5)` için de `double`'dan `long`'a standart dönüşüm vardır
ve üç noktadan iyidir. Bu sıralamayı belirleyen standarttır; çıktı yalnız onu doğrular.

#### Öncelik Etiketi ile Aday Sıralamak

`enable_if` ile elenen adayların birden fazlası aynı anda uygunsa çağrı belirsizleşir.
Öncelik kurmak için eski bir yöntem, etiket tiplerinden bir türetme zinciri kurmaktır.
`oncelik<2>`, `oncelik<1>`'den, o da `oncelik<0>`'dan türer. Her aday son parametre olarak
farklı bir etiket alır ve çağrı en yüksek etiketle yapılır: tam uyan en yüksek etikettir,
elenenler listeden düşer, kalanlar arasından türetme mesafesi en kısa olan seçilir.

```cpp
// oncelik.cpp — etiketle aday sirasi
#include <cstdio>
#include <string>
#include <type_traits>
#include <utility>

template <int N>
struct oncelik : oncelik<N - 1> {};
template <>
struct oncelik<0> {};

template <typename T>
auto ayir(const T& x, oncelik<2>) -> decltype(x.size(), "boyutlu") {
    return "boyutlu";
}

template <typename T, std::enable_if_t<std::is_pointer_v<T>, int> = 0>
const char* ayir(const T&, oncelik<1>) { return "isaretci"; }

template <typename T>
const char* ayir(const T&, oncelik<0>) { return "genel"; }

template <typename T>
const char* ayir(const T& x) { return ayir(x, oncelik<2>{}); }

int main() {
    int n = 3;
    std::printf("string  %s\n", ayir(std::string("ab")));
    std::printf("isaretci %s\n", ayir(&n));
    std::printf("int     %s\n", ayir(n));
    return 0;
}
```

```text
string  boyutlu
isaretci isaretci
int     genel
```

**MP28.** `std::string` için iki aday uygundur, çünkü işaretçi adayı `enable_if` ile
elenir; `oncelik<2>` en yüksek etikettir ve bir
türetme adımı gerektirmez, `boyutlu` kazanır. `int*` için `size()` sorgusu bildirimde
başarısız olur, ilk aday elenir ve `oncelik<1>` adayı seçilir. `int` için ikisi de
elenir ve `genel` kalır. C++17 `if constexpr` ve C++20 kavramları bu zincirin yerini alır;
ama etiket yöntemi eski kodda hâlâ yaygındır, o yüzden okuyabilmek gerekir.

#### void_t ile Algılama

`enable_if` bir koşulu açıkça yazar. Bazen soru, bir ifadenin tip için geçerli olup
olmadığıdır. `std::void_t<...>` bu soruyu SFINAE'ye bağlar: argümanları ne olursa olsun
`void` olur, ama argümanlardan biri geçersizse yerine koyma başarısız olur. Genel şablon
`false_type`'tan türer; kısmi özelleştirme `void_t` içinde ifadeyi dener.

```cpp
// bolunur.cpp — void_t ile algilama
#include <cstdio>
#include <string>
#include <type_traits>
#include <utility>

template <typename T, typename = void>
struct bolunur : std::false_type {};

template <typename T>
struct bolunur<T, std::void_t<decltype(std::declval<T>() / 2)>> : std::true_type {};

int main() {
    std::printf("bolunur<int>         %s\n", bolunur<int>::value ? "evet" : "hayir");
    std::printf("bolunur<double>      %s\n", bolunur<double>::value ? "evet" : "hayir");
    std::printf("bolunur<std::string> %s\n", bolunur<std::string>::value ? "evet" : "hayir");
    return 0;
}
```

```text
bolunur<int>         evet
bolunur<double>      evet
bolunur<std::string> hayir
```

`std::declval<T>() / 2` ifadesi `int` için geçerlidir, `std::string` için geçersizdir.
Geçersizse kısmi özelleştirme elenir ve genel şablon kalır. Bu, ders başında sorulan
soruya cevaptır: uygun olmayan aday hata vermez, aday listesinden düşer.

#### C++20: Kavram ve requires

`enable_if` ve `void_t` işi görür ama okunması zordur. C++20, aynı kısıtı doğrudan
yazmak için **kavram** ve `requires` ifadesi getirdi. Bir **kısıt** sağlanmıyorsa şablon
aday olmaz, derleyiciler de hata iletisinde genellikle uzun bir yerine koyma dökümü yerine sağlanmayan
kısıtı gösterir.

```cpp
// kavram.cpp — requires ile kisit
#include <cstdio>
#include <string>

template <typename T>
concept Bolunebilir = requires(T x) { x / 2; };

template <Bolunebilir T>
const char* yaris(T) { return "bolunur"; }

const char* yaris(...) { return "bolunmez"; }

int main() {
    std::printf("yaris(8)   %s\n", yaris(8));
    std::printf("yaris(str) %s\n", yaris(std::string("x")));
    return 0;
}
```

```text
yaris(8)   bolunur
yaris(str) bolunmez
```

Kısıtlı şablon `int` için uygun adaydır ve üç noktalı aday daha az iyi olduğu için
kazanır. `string` için kısıt sağlanmaz, şablon elenir ve geriye üç noktalı aday kalır.
Kavram bir sorguyu adlandırır; seçim yine derlemede biter.

#### Elenen Adayın Gövdesi Yazılmaz

SFINAE'nin bedelinin neresinde olduğunu sayıyla görelim. Bir aday elenirse, gövdesi
hiçbir zaman somutlaştırılmaz. Aşağıda iki adayın gövdesi yerel bir `static` ile kayıt
altına alınır; çağrılmayan gövde kayıt bırakmaz.

```cpp
// govde-sayisi.cpp — elenen adayin govdesi
#include <cstdio>
#include <type_traits>

inline int tamsayi_govde = 0;
inline int kayan_govde = 0;

template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
int ikiye(T x) {
    static const bool kayit = (++tamsayi_govde, true);
    (void)kayit;
    return static_cast<int>(x) * 2;
}

template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
int ikiye(T x) {
    static const bool kayit = (++kayan_govde, true);
    (void)kayit;
    return static_cast<int>(x * 2);
}

int main() {
    ikiye(7);
    ikiye(8);
    ikiye(9L);
    std::printf("tamsayi=%d kayan=%d\n", tamsayi_govde, kayan_govde);
    ikiye(2.5);
    std::printf("tamsayi=%d kayan=%d\n", tamsayi_govde, kayan_govde);
    return 0;
}
```

```text
tamsayi=2 kayan=0
tamsayi=2 kayan=1
```

**MP29.** Üç tamsayı çağrısından sonra yalnız tamsayı gövdeleri vardır: `int` için bir,
`long` için bir, yani iki gövde; kayan gövde sıfırdır. Sonra bir `double` çağrısı kayan
gövdeyi bir yapar. Elenen aday listeden düşmüştür ve gövdesi hiç yazılmamıştır; bu yüzden
SFINAE'nin bedeli yalnız elemenin kendisidir. Sayılar bir ürün sayısıdır, bir süre
değil.

#### Kavramlarda Sıralama: Kapsama

Kavramlarla çalışan C++20 çözümlemesi, aynı adayın iki kısıtlı sürümü uygunsa, daha çok
kısıtlananı seçer. Bir kavram bir başkasını içeriyorsa (`std::signed_integral`,
`std::integral`'ı kapsar) kapsayan kavram daha özeldir. Bu kural, etiket zincirinin
yerini alır: sıra, kavramların kendi yapısından çıkar.

```cpp
// kisit-sira.cpp — kavram kapsamasi
#include <concepts>
#include <cstdio>

template <typename T>
const char* sinif(T) { return "genel"; }

template <std::integral T>
const char* sinif(T) { return "tamsayi"; }

template <std::signed_integral T>
const char* sinif(T) { return "isaretli"; }

int main() {
    std::printf("sinif(1)   %s\n", sinif(1));
    std::printf("sinif(1u)  %s\n", sinif(1u));
    std::printf("sinif(1.5) %s\n", sinif(1.5));
    return 0;
}
```

```text
sinif(1)   isaretli
sinif(1u)  tamsayi
sinif(1.5) genel
```

**MP30.** `int` hem `integral` hem `signed_integral`'dır; daha çok kısıtlı olan `isaretli`
seçilir. `unsigned` yalnız `integral`'dır, `tamsayi` kalır. `double` hiçbirini sağlamaz,
`genel` kalır. Bu seçim yine derlemededir ve çağrı noktasında bir sorgu bırakmaz. Bedel
**derleme** sınıfındadır; yalnız adayların toplanması ve kısıtların denetlenmesi
derleyicinin işine eklenir.

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

SFINAE yalnız bildirimin **hemen bağlamındaki** yerine koyma hatalarını kapsar: dönüş
tipi, parametre tipleri ve şablon parametreleri yazılırken doğrudan çıkan hatalar. Bu
sırada başka bir şablonun somutlaştırılması gerekir ve hata onun içinde çıkarsa, eleme
olmaz, derleme durur. İşlevin gövdesi aday seçiminden sonra somutlaştırılır. Gövdedeki
bir hata artık aday listesinden eleme yapmaz, derlemeyi durdurur. Örneğin `x.size()`
çağrısını gövdede yazan bir şablon `int` ile çağrılırsa, derleyici o şablonu uygun aday
sayar, seçer, gövdeyi somutlaştırırken hata verir ve derleme durur.

Aynı çağrı bildirime taşınınca elemeye dönüşür. Aşağıdaki sürüm, `size()` sorgusunu
dönüş tipinde yapar.

```cpp
// bildirim.cpp — sorgu bildirimde, gövde hatasiz
#include <cstdio>
#include <string>

template <typename T>
auto boyut(const T& x) -> decltype(x.size(), long()) {
    return static_cast<long>(x.size());
}

long boyut(...) { return -1; }

int main() {
    std::printf("string %ld\n", boyut(std::string("abcd")));
    std::printf("int    %ld\n", boyut(7));
    return 0;
}
```

```text
string 4
int    -1
```

**MP31.** `string` için dönüş tipi geçerlidir ve şablon seçilir. `int` için `x.size()`
geçersizdir, yerine koyma başarısız olur ve üç noktalı aday kalır. Aynı sorgu gövdede
olsaydı derleme dururdu. Bu yüzden sayı eleme olayının kanıtıdır; ölçü sorusunun yanıtı bu
elemenin yapıldığı aşamadır: **derleme**.

#### Özet

- Bu dersin ölçtüğü düzenek bedeli **derleme** sınıfına taşır: aday toplama, uygun
  adayı eleme ve en iyi adayı seçme derlemede biter, koşumda sorgu kalmaz.
- Aday, uygun aday ve en iyi aday sırasıyla toplanır, elenir ve seçilir; eşitlikte şablon
  olmayan kazanır, tam uyum dönüşümü yener.
- SFINAE'de yerine koyma başarısızlığı hata değildir: `enable_if_t` ile
  `tur(7)` tamsayi, `tur(2.5)` kayan seçilir.
- `void_t` bir ifadenin geçerliliğini algılar: `bolunur<int>` evet, `bolunur<std::string>`
  hayir.
- C++20 `concept` ve `requires` aynı kısıtı okunur yazar; SFINAE yalnız bildirimi kapsar,
  gövdedeki hata derlemeyi durdurur.

#### Sonraki Adım

Bu derste aday elemesi derlemede bitti ve koşumda hiçbir sorgu bırakmadı. Sıradaki ders
bu fikri bir sınıf hiyerarşisine uyguluyor: sanal işlev yerine şablon kullanılırsa
gönderim derlemeye taşınır, ama her tip için ayrı gövde yazdırılır. Bu değiş tokuşun
ölçüsü ne?

### Statik Çok Biçimlilik

Source: https://academia.sh/tr/kurslar/cpp-sablonlar/statik-cok-bicimlilik

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?

## Eşzamanlılık ve Başarım

İş parçacıkları ve kilitler, bellek modeli, önbellek dostu veri düzeni, profilleme ve karşılaştırmalı ölçüm, derleme süresi yönetimi, paket yönetimi ve derleme araçları — her biri bedelin nereye taşındığı sorusuyla birlikte.

### İş Parçacıkları ve Kilitler

Source: https://academia.sh/tr/kurslar/cpp-sablonlar/is-parcaciklari-ve-kilitler

std::thread ve join, std::jthread, std::mutex ile std::lock_guard, std::scoped_lock, std::unique_lock ile std::condition_variable, üretici/tüketici kuyruğu ve kilidin kaç eşgüdüm noktası eklediğinin sayılması.

Önceki ders, tip kümesi derlemede kapalıysa çok biçimliliğin bedelinin derlemeye
taşındığını gösterdi: işlev çağrısı koşarken hiçbir şey ödemez, ama somutlaştırılan her
tip için ayrı bir gövde üretilir. Bu derste ödemenin başka bir yere gittiği durumu
ölçüyoruz: iş birden çok iş parçacığına bölününce ortak veriyi korumak için eklenen
kilitler. Kursun ölçtüğü soru burada da aynı kalıyor: bir düzenek bedeli nereye taşıyor?
Cevabın sınıfı **bekleme**dir. Bu ölçü gerçek beklemeyi değil, bir iş parçacığının
**beklemek zorunda kalabileceği nokta** sayısını sayar; süre basılmaz, çünkü süre
makineye ve o anki yüke bağlıdır, nokta sayısı ise koddan okunur.

Bir ayrımı baştan söyleyelim. M10/K03'te "kaç kez koştu" sorusunda sayı ölçünün kendisiydi.
Burada sayı yalnızca kanıt aracıdır: ders "kilit kaç kez alındı" sayısına değil, o
sayının gösterdiği sınıfa, yani **bekleme**ye cevap verir. Ayrıca M08/K04'ün sorusu olan
"adımı hangi iş parçacığı koşturuyor" bu derste sorulmaz; hangi iş parçacığının ne
yaptığı değil, kaç eşgüdüm noktası olduğu ölçülür.

#### İş Parçacığı Açmak ve Beklemek

`std::thread`, kurulduğu anda verilen işlevi ayrı bir iş parçacığında koşturmaya başlar.
Nesne yıkılmadan önce ya `join()` ile beklenmeli ya da `detach()` ile bırakılmalıdır;
ikisinden hiçbiri yapılmadan yıkılan nesne programı sonlandırır. Bu yüzden her örnekte
`join()` çağrılır ve sonuç yalnızca `join()` döndükten sonra okunur: `join()` iş
parçacığının yazdığı her şeyin çağırana görünür olduğunu garanti eder.

Ortak veriye hiçbir koruma olmadan iki iş parçacığı yazarsa, programın davranışı
**tanımsız**dır: buna veri yarışı denir. Dört iş parçacığının kilitsiz bir sayacı onar
bin kez artırması, sonucun kırk bin olacağını garanti etmez; hiçbir sonucu garanti etmez.
Tanımsız davranışı koşturmuyoruz; yalnızca tanımlı karşılığını koşturup düzyazıda
neden gerektiğini anlatıyoruz.

#### jthread: Kendiliğinden Katılan İş Parçacığı

`std::jthread` C++20'yle gelir ve `std::thread`'in en sık yapılan hatasını kaldırır.
Yıkıcısı, iş parçacığı henüz katılmamışsa önce bir durdurma isteği gönderir, sonra
`join()` çağırır; araya bir `return` ya da bir
istisna girse bile iş parçacığı kapsamdan çıkmadan bitirilir. Katılmadan yıkılan bir
`std::thread` ise programı sonlandırır. Fark yazım kolaylığından çok **güvenliktir**:
unutulan bir `join()` artık bir çökme olmaz.

İkinci fark, `jthread`'in bir durdurma isteği taşımasıdır. İşlev bir `std::stop_token`
parametresi alırsa, dışarıdan `request_stop()` çağrıldığında döngüsünden çıkabilir.
İsteğin ne zaman görüleceği zamanlamaya bağlı olduğundan bunu koşturmuyoruz; yalnızca
kapsamın sonunda katılma davranışını kullanıyoruz. Ölçümün konusu iş parçacığının nasıl
açıldığı değil, açıldıktan sonra ortak veriyi nasıl koruduğumuzdur.

#### Kilit ve lock_guard

Tanımlı karşılık, ortak veriyi bir dışlama kilidinin arkasına almaktır. `std::mutex`
kilitlendiğinde, kilidi bırakana kadar başka hiçbir iş parçacığı onu alamaz; alamayan
iş parçacığı bekler. `std::lock_guard` kilidi kurulduğunda alır, yıkıldığında bırakır;
bırakma işini kapsamın sonuna bağladığı için bir `return` ya da bir istisna kilidi
açık bırakamaz.

Aşağıdaki başlık dosyası ortak sayacı ve onu artırmanın iki yolunu tanımlar. `alma`,
kilidin kaç kez alındığını sayan ikinci alandır; ona yalnızca kilit tutulurken
dokunulur. İlk yol her artışta kilit alır. İkincisini birazdan açıklayacağız.

```cpp
// kilit_olcum.hpp — ortak sayac ve iki artirma bicimi
#pragma once
#include <mutex>

struct KilitliSayac {
    std::mutex kilit;
    long deger = 0;
    long alma = 0;
};

inline void artir_her_seferde(KilitliSayac& s, int kez) {
    for (int i = 0; i < kez; ++i) {
        std::lock_guard<std::mutex> koruma(s.kilit);
        ++s.deger;
        ++s.alma;
    }
}

inline void artir_yerel(KilitliSayac& s, int kez) {
    long yerel = 0;
    for (int i = 0; i < kez; ++i) ++yerel;
    std::lock_guard<std::mutex> koruma(s.kilit);
    s.deger += yerel;
    ++s.alma;
}
```

#### Birden Çok Kilit ve scoped_lock

Tek kilit tek değişkeni korur. İki nesneyi birden değiştiren bir işlem, ikisinin de
kilidini almak zorundadır. İki iş parçacığı bu kilitleri ters sırayla alırsa, her biri
öbürünün tuttuğu kilidi bekler ve hiçbiri ilerleyemez: buna kilitlenme denir.
Kilitlenmenin ortak kökü, kilitlerin **tutarsız sırayla** alınmasıdır.

İki çıkış yolu vardır. Birincisi bütün kodda tek bir sıra belirlemektir; örneğin kilitleri
her zaman hesap numarasına göre küçükten büyüğe almak. Bu yol çalışır ama disiplin ister:
sıra yalnızca bir kuraldır ve yeni yazılan her işlev ona uymak zorundadır. İkincisi
`std::scoped_lock`'tur: birden çok kilidi tek seferde, kilitlenmeyi önleyen bir sırayla
alır; standart bunun için kilitlenmeden kaçınan bir algoritma kullanılmasını ister. Kilitleri
hangi sırayla yazdığınız artık önemli olmaz.

```cpp
// hesap.hpp — iki ve uc kilidi birden alan islemler
#pragma once
#include <mutex>

struct Hesap {
    std::mutex kilit;
    long bakiye = 0;
};

inline void aktar(Hesap& kaynak, Hesap& hedef, long miktar) {
    std::scoped_lock ikisi(kaynak.kilit, hedef.kilit);
    kaynak.bakiye -= miktar;
    hedef.bakiye += miktar;
}

inline long toplam_bakiye(Hesap& a, Hesap& b, Hesap& c) {
    std::scoped_lock uclu(a.kilit, b.kilit, c.kilit);
    return a.bakiye + b.bakiye + c.bakiye;
}
```

`aktar(a, b, 1)` ile `aktar(b, a, 1)` aynı anda çağrılsa bile kilitlenme olmaz. Elle
`a.kilit.lock()` ardından `b.kilit.lock()` yazılsaydı, iki çağrı ters sırada alırsa
kilitlenme mümkün olurdu; bunu koşturmuyoruz, çünkü sonucun programı asılı bırakması
mümkündür. `toplam_bakiye` üç kilidi birden alır: aktarmalar yalnızca iki kilit tutar,
bu yüzden toplamı okuyan, hiçbir aktarmanın ortasına denk gelmez.

#### Koşul Değişkeni ve unique_lock

Bazen bir iş parçacığının ortak veriyi korumasının ötesinde, **bir şeyin olmasını
beklemesi** gerekir. `std::condition_variable` bunun içindir: bekleyen iş parçacığı
kilidi bırakıp uyur, başka bir iş parçacığı koşulu sağlayıp haber verince uyanır. Bekleyen
tarafın kilidi elinde tutup bırakabilmesi için `std::lock_guard` yetmez; bırakılıp yeniden
alınabilen `std::unique_lock` gerekir. `unique_lock`, `lock_guard`'ın yaptığı her şeyi
yapar ve üstüne `unlock()` ile `lock()` çağrılabilir; bedeli küçük bir ek durum bilgisidir.

```cpp
// teslim.hpp — kosul degiskeniyle bir kez uyanma
#pragma once
#include <condition_variable>
#include <mutex>

struct Teslim {
    std::mutex kilit;
    std::condition_variable uyandir;
    bool hazir = false;
    int deger = 0;
};

inline void hazirla(Teslim& t, int v) {
    {
        std::lock_guard<std::mutex> koruma(t.kilit);
        t.deger = v;
        t.hazir = true;
    }
    t.uyandir.notify_one();
}

inline int bekle(Teslim& t) {
    std::unique_lock<std::mutex> kilitli(t.kilit);
    t.uyandir.wait(kilitli, [&t] { return t.hazir; });
    return t.deger;
}
```

`wait` koşulu bir koşul ifadesiyle alır ve kilit elde iken denetler; uyanma haberi
boşuna gelmiş ya da gelmeden önce de koşul sağlanmış olabilir, bu yüzden koşul her
uyanışta yeniden sınanır. Bayrak ve değer yalnızca kilit altında yazılıp okunur.

#### Üretici ve Tüketici

Tek bir değer yerine bir değer akışı taşındığında aynı düzenek bir kuyruğa dönüşür.
Üretici kuyruğa değer koyar ve haber verir; tüketici kuyruk doluyken değerleri alır,
boşken uyur. İş bittiğinde üretici bir `bitti` bayrağı kaldırır, çünkü tüketicinin
"kuyruk boş, ama daha değer gelecek mi" sorusuna başka cevabı yoktur.

Tüketici değeri kuyruktan aldıktan sonra kilidi **bırakır**, işi kilitsiz yapar ve kilidi
yeniden alır. Bu, `unique_lock`'un `lock_guard`'dan ayrıldığı yerdir: kilit yalnızca
ortak veriye dokunulan süre boyunca tutulur, değerin işlenmesi kuyruğu başkalarına
kapatmaz.

```cpp
// kuyruk.hpp — uretici/tuketici kuyrugu
#pragma once
#include <condition_variable>
#include <mutex>
#include <queue>

struct Kuyruk {
    std::mutex kilit;
    std::condition_variable uyandir;
    std::queue<int> ogeler;
    bool bitti = false;
};

inline void uret(Kuyruk& k, int adet) {
    for (int i = 0; i < adet; ++i) {
        {
            std::lock_guard<std::mutex> koruma(k.kilit);
            k.ogeler.push(i);
        }
        k.uyandir.notify_one();
    }
    {
        std::lock_guard<std::mutex> koruma(k.kilit);
        k.bitti = true;
    }
    k.uyandir.notify_one();
}

inline long tuket(Kuyruk& k, int& alinan) {
    long toplam = 0;
    std::unique_lock<std::mutex> kilitli(k.kilit);
    for (;;) {
        k.uyandir.wait(kilitli, [&k] { return !k.ogeler.empty() || k.bitti; });
        if (k.ogeler.empty()) break;
        int v = k.ogeler.front();
        k.ogeler.pop();
        kilitli.unlock();
        toplam += v;
        ++alinan;
        kilitli.lock();
    }
    return toplam;
}
```

Tüketicinin kaç kez uyanıp kaç kez beklediği çizelgeye bağlıdır ve basılmaz. Basılan,
çizelgeden bağımsız iki sayıdır: kaç değerin alındığı ve değerlerin toplamı.

#### Ölçüm: Kilit Kaç Kez Alındı

Şimdi iki artırma biçimini dört iş parçacığıyla, her biri on bin artış yapacak biçimde
koşturuyoruz. Aynı programda `aktar`, `Teslim` ve `Kuyruk` da çalışır; her sonuç yalnızca
`join()` döndükten sonra basılır, böylece çıktı iş parçacıklarının sırasından bağımsızdır.

```cpp
// kilit_olcumu.cpp — kilit alma sayisi ve yardimci sinamalar
#include <functional>
#include <iostream>
#include <thread>
#include <vector>

#include "hesap.hpp"
#include "kilit_olcum.hpp"
#include "kuyruk.hpp"
#include "teslim.hpp"

namespace {

void dort_is_parcacigi(KilitliSayac& s, void (*islev)(KilitliSayac&, int)) {
    std::vector<std::thread> isler;
    for (int i = 0; i < 4; ++i) isler.emplace_back(islev, std::ref(s), 10000);
    for (auto& is : isler) is.join();
}

}  // namespace

int main() {
    KilitliSayac her_seferde;
    dort_is_parcacigi(her_seferde, artir_her_seferde);
    std::cout << "kilitli sayac bekleme alma=" << her_seferde.alma
              << " deger=" << her_seferde.deger << "\n";

    KilitliSayac yerel;
    dort_is_parcacigi(yerel, artir_yerel);
    std::cout << "yerel toplam bekleme alma=" << yerel.alma
              << " deger=" << yerel.deger << "\n";

    Hesap a, b;
    a.bakiye = 1000;
    b.bakiye = 1000;
    {
        std::thread ileri([&] { for (int i = 0; i < 500; ++i) aktar(a, b, 1); });
        std::thread geri([&] { for (int i = 0; i < 500; ++i) aktar(b, a, 1); });
        ileri.join();
        geri.join();
    }
    std::cout << "iki yonlu aktarma bekleme toplam=" << a.bakiye + b.bakiye << "\n";

    Hesap x, y, z;
    x.bakiye = y.bakiye = z.bakiye = 1000;
    int ihlal = 0;
    {
        std::thread t1([&] { for (int i = 0; i < 300; ++i) aktar(x, y, 1); });
        std::thread t2([&] { for (int i = 0; i < 300; ++i) aktar(y, z, 1); });
        std::thread t3([&] { for (int i = 0; i < 300; ++i) aktar(z, x, 1); });
        std::thread denetci([&] {
            for (int i = 0; i < 200; ++i)
                if (toplam_bakiye(x, y, z) != 3000) ++ihlal;
        });
        t1.join();
        t2.join();
        t3.join();
        denetci.join();
    }
    std::cout << "uc hesap halkasi bekleme toplam=" << toplam_bakiye(x, y, z)
              << " ihlal=" << ihlal << "\n";

    Teslim teslim;
    int alinan = 0;
    {
        std::jthread tuketici([&] { alinan = bekle(teslim); });
        hazirla(teslim, 42);
    }
    std::cout << "kosul degiskeni bekleme alinan=" << alinan << "\n";

    Kuyruk kuyruk;
    int alinan_oge = 0;
    long oge_toplami = 0;
    {
        std::jthread tuketici([&] { oge_toplami = tuket(kuyruk, alinan_oge); });
        uret(kuyruk, 1000);
    }
    std::cout << "uretici tuketici bekleme alinan=" << alinan_oge
              << " toplam=" << oge_toplami << "\n";
    return 0;
}
```

```text
kilitli sayac bekleme alma=40000 deger=40000
yerel toplam bekleme alma=4 deger=40000
iki yonlu aktarma bekleme toplam=2000
uc hesap halkasi bekleme toplam=3000 ihlal=0
kosul degiskeni bekleme alinan=42
uretici tuketici bekleme alinan=1000 toplam=499500
```

**EB1.** İlk satır dört iş parçacığının kırk bin artışının her birinde kilit aldığını
gösterir: `alma=40000`, `deger=40000`. Her kilit alma, başka bir iş parçacığının
tuttuğu kilidi beklemek zorunda kalabileceği bir noktadır; ders bu noktaların
sayısını ölçüyor, gerçekte kaç kez beklendiğini değil. Sonuç `deger=40000` tanımlıdır
çünkü her artış kilit altında yapıldı; bu doğruluk kilidin verdiği garantidir.

**EB2.** Üçüncü satırdaki `toplam=2000`, iki yönde ters sırayla yapılan aktarmaların
`std::scoped_lock` sayesinde ne kilitlenme ne de kayıp bıraktığını gösterir: iki
bakiyenin toplamı değişmedi. Burada kilitleri hangi sırayla yazdığımızın bir önemi
yoktu; `aktar(a, b, …)` ve `aktar(b, a, …)` aynı iki kilidi zıt sırayla istedi.

**EB3.** Dördüncü satır üç kilidin bir halkasını sınar: `x`'ten `y`'ye, `y`'den `z`'ye ve
`z`'den `x`'e aktarma yapan üç iş parçacığı çalışırken, dördüncü iş parçacığı iki yüz
kez üç kilidi birden alıp toplamı okudu. Toplam her seferinde `3000` göründü ve
`ihlal=0` çıktı. Okuyan hiçbir aktarmanın ortasına denk gelmedi, çünkü hiçbir aktarma
iki kilidin yalnızca birini tutarken bakiyeyi değiştirmedi.

**EB4.** Beşinci satırda bekleyen iş parçacığı `hazirla` çağrılınca uyandı ve `42`
değerini aldı. `std::jthread` kapsamın sonunda `join()` yaptı, bu yüzden `alinan`
okunmadan önce yazılmış oldu. Uyanmanın hangi anda olduğu basılmadı; yalnızca ne alındığı
basıldı.

**EB5.** Son satır, tüketicinin bin değerin hepsini aldığını ve toplamın `499500`
olduğunu gösterir; bu, sıfırdan dokuz yüz doksan dokuza kadar sayıların toplamıdır.
Hiçbir değer kaybolmadı ve hiçbiri iki kez alınmadı. `bitti` bayrağı, kuyruk boşalsa
bile üretici henüz bitmemişken tüketicinin çıkmamasını, üretici bitip kuyruk boşalınca da
beklemekte takılı kalmamasını sağladı.

**EB6.** Beşinci ve altıncı satırdaki iki `std::jthread` de elle `join()` yazılmadan
bitirildi: kapsam kapanırken yıkıcı katıldı. Bu, sonucun okunabilir olmasının tek
dayanağıdır; `join()` ya da onu çağıran yıkıcı olmasaydı, `alinan` ve `oge_toplami`
okunurken iş parçacığı hâlâ yazıyor olabilirdi.

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

Bu dersin ölçtüğü düzenek, ortak veriyi kilitle korumaktır; ve ikinci satır bu
düzeneğin bedelini düşürür. `artir_yerel` her iş parçacığında on bin artışı kendi yerel
toplamında yapar, kilidi yalnızca sonunda bir kez alıp toplamı ortak sayaca ekler.
Sonuç aynıdır, `deger=40000`; ama eşgüdüm noktası kırk binden **dört**e düştü:
`alma=4`. Eşgüdüm noktası on bin kat azaldı, doğruluk korundu.

Bundan çıkan ders, bedelin kilidin kendisinden değil **kaç kez alındığından** geldiğidir.
Aynı `std::mutex` iki ayrı yazımda kırk bin ya da dört kez alındı. Bunun sınırı da
şudur: yerel toplam yalnızca işin parçalara ayrılıp sonda birleştirilebildiği durumda
çalışır. Her adım ortak durumu görmek zorundaysa, yani bir iş parçacığının sonucu
öbürünün bir sonraki adımına girdiyse, kilit her adımda alınır ve sayı düşmez.
Bu durumda bedeli düşürmenin yolu kilidi bırakmak değil, ortak durumu bölmektir; bunu
sonraki iki derste bellek sıralaması ve veri düzeni olarak göreceğiz.

#### Özet

- Bu dersin ölçtüğü düzenek bedeli **bekleme** sınıfına taşır: kilit, bir iş parçacığının
  beklemek zorunda kalabileceği nokta sayısını artırır; ölçü gerçek bekleme süresi değil,
  bu noktaların sayısıdır.
- `std::thread` kurulduktan sonra `join()` ile beklenir; iş parçacığının yazdığı her şey
  yalnızca `join()` döndükten sonra okunur. `std::jthread` aynı işi yıkıcısında yapar.
- Ortak veriye korumasız yazmak veri yarışıdır ve davranışı tanımsızdır;
  `std::mutex` ile `std::lock_guard` bu yarışı bitirir, kilidi kapsamın sonunda bırakır.
- `std::scoped_lock` birden çok kilidi kilitlenmeye yol açmayan bir sırayla alır; sıra
  disiplini gerekmez.
- `std::condition_variable` bir koşulu beklemek içindir ve `std::unique_lock` ister;
  koşul her uyanışta yeniden sınanır. `unique_lock` kilidi bırakıp yeniden alabildiği
  için üretici/tüketici kuyruğunda işin kilitsiz yapılmasına izin verir.
- `kilitli sayac` kırk bin kilit alması, `yerel toplam` dört kilit alması ile aynı sonucu
  verdi: bedeli kilit değil, kaç kez alındığı belirler.

#### Sonraki Adım

Kilit, bir verinin kimin elinde olduğunu söyler ve bir iş parçacığının kilit altında
yazdığını kilidi sonra alana görünür kılar; ama görünürlük için hangi sıralamanın gerçekten
gerektiğini ve bunun kaç eşgüdüm noktasına mal olduğunu söylemez. Bir sonraki ders bellek modelini açıyor: bir
bellek sıralaması kaç eşgüdüm noktası istiyor sorusuyla `std::atomic`, release ve acquire
bağıntısını ölçeceğiz.

### Bellek Modeli

Source: https://academia.sh/tr/kurslar/cpp-sablonlar/bellek-modeli

std::atomic, okuma-değiştirme-yazma işlemleri, seq_cst, acquire, release ve relaxed bellek sıralamaları, önce-olur bağıntısı ve bir release/acquire yayınının kaç eşgüdüm noktası istediğinin sayılması.

Önceki ders ortak veriyi kilitle korumayı ve kilidin bedelinin kaç kez alındığıyla
belirlendiğini gösterdi: dört iş parçacığı için kırk bin kilit alma, yerel toplamla dörde
inebildi. Kilit her şeyi birden yapar: veriyi tek bir iş parçacığına tutturur ve
yazılanların sonraki alana görünmesini sağlar. Bu ders o ikisini ayırıyor. `std::atomic`
bir değişkene yapılan tek bir işlemi bölünmez kılar; ayrıca bu işlemin **çevresindeki**
yazıların başkasına ne zaman görünür olacağını bellek sıralaması belirler. Kursun
ölçtüğü soru yine aynı: düzenek bedeli nereye taşıyor? Cevabın sınıfı **bekleme**dir ve
ölçü burada da gerçek beklemeyi değil, bir veri yayınının kaç eşgüdüm noktası
istediğini sayar. Sayı kanıt aracıdır; ders sınıf adıyla cevaplanır.

#### Bölünmezlik ile Sıralama Aynı Şey Değildir

`std::atomic<int>` üzerindeki bir okuma ya da yazma, yarım görülemez: başka bir iş
parçacığı ya eski değeri ya yeni değeri görür, arası yoktur. `fetch_add` gibi
okuma-değiştirme-yazma işlemleri bu bölünmezliği üç adımın tamamına yayar: değeri okuma,
artırma ve geri yazma tek işlemdir, araya başka bir artış giremez. Bu yüzden kilitsiz
bir sayaç artırmak, değişken `std::atomic` olduğunda veri yarışı sayılmaz.

Ama bölünmezlik, **diğer** değişkenlere ne olduğunu söylemez. Her atomik işlem bir
bellek sıralaması alır ve dört sıralama bu derste önemlidir. `std::memory_order_seq_cst`
varsayılandır: bütün `seq_cst` işlemleri bütün iş parçacıkları için tek bir ortak
sırayla görünür. `memory_order_release` bir yazıya, `memory_order_acquire` bir okumaya
verilir ve ikisi birlikte çalışır. `memory_order_relaxed` ise yalnızca bölünmezlik verir,
başka hiçbir şey sıralamaz.

Release ve acquire arasındaki ilişki, **önce-olur bağıntısı** olarak ifade edilir. Bir
iş parçacığı bir atomik değişkene release ile yazarsa ve başka bir iş parçacığı o
yazılan değeri acquire ile okursa, release'ten önceki bütün yazılar acquire'dan sonraki
bütün okumalara görünür olur. Düz, atomik olmayan değişkenler de buna dahildir; yayın
bayrağı atomik, yayınlanan veri düz olabilir.

#### Bir Yayın: Bin Düz Değer, Bir Release Yazısı

Üretici bin düz değeri bir diziye yazar, sonra yalnızca bir atomik bayrağa release ile
yazar. Tüketici bayrağı acquire ile okuyup birini görünce bin değerin hepsini görür.
Eşgüdüm noktası, üreticide yapılan release yazılarının sayısıdır; düzenek bir yazı için
bir nokta ister. Sayacı elle yazmıyoruz: her release yazısından hemen sonra üretici
tarafı kendi düz sayacını bir artırır ve sayaç yalnızca `join()` sonrasında okunur.

```cpp
// yayin.hpp — iki yayin bicimi: tek bayrak ve her eleman atomik
#pragma once
#include <atomic>
#include <thread>

constexpr int ELEMAN = 1000;

struct TekBayrak {
    int veri[ELEMAN];
    std::atomic<int> hazir{0};
    long esgudum = 0;
    long toplam = 0;
};

inline void uret_tek(TekBayrak& y) {
    for (int i = 0; i < ELEMAN; ++i) y.veri[i] = i;
    y.hazir.store(1, std::memory_order_release);
    ++y.esgudum;
}

inline void tuket_tek(TekBayrak& y) {
    while (y.hazir.load(std::memory_order_acquire) == 0) std::this_thread::yield();
    long t = 0;
    for (int i = 0; i < ELEMAN; ++i) t += y.veri[i];
    y.toplam = t;
}

struct HerEleman {
    std::atomic<int> dizi[ELEMAN];
    long esgudum = 0;
    long toplam = 0;
    HerEleman() {
        for (auto& e : dizi) e.store(-1, std::memory_order_relaxed);
    }
};

inline void uret_hepsi(HerEleman& y) {
    for (int i = 0; i < ELEMAN; ++i) {
        y.dizi[i].store(i, std::memory_order_release);
        ++y.esgudum;
    }
}

inline void tuket_hepsi(HerEleman& y) {
    long t = 0;
    for (int i = 0; i < ELEMAN; ++i) {
        int v;
        while ((v = y.dizi[i].load(std::memory_order_acquire)) == -1) std::this_thread::yield();
        t += v;
    }
    y.toplam = t;
}
```

`TekBayrak` dizisi düz bir `int` dizisidir; `hazir` bayrağı atomiktir. Üretici bayraktan
önce yazar, tüketici bayrağı görmeden dizinin hiçbir elemanına dokunmaz. `HerEleman`
ikinci biçimdir ve Sınırlayıcı Ölçüm bölümünde okunacak. Dizinin sıfırlanması, iş
parçacıkları açılmadan önce yapılır: `std::thread` kurulumu, yapıcının çağrılmasına kadar yapılan
her şeyi yeni iş parçacığına görünür kılar.

#### Relaxed Neyi Vermez

`relaxed` sıralama, bölünmezlik haricinde hiçbir şey vermez. Bu, relaxed bir `fetch_add`
sayacının yine de doğru toplam vermesi demektir: dört iş parçacığı onar bin kez
`fetch_add(1, std::memory_order_relaxed)` çağırırsa sonuç kırk bin olur, çünkü
bölünmezlik her artışı korur. Ama aynı `relaxed` işlem bir yayın bayrağı olarak
kullanılırsa üreticinin düz yazıları tüketiciye görünmeyebilir; bayrak `1` görünmüş ama
bin değer eski halinde kalmış olabilir. Bu bir veri yarışıdır ve davranışı tanımsızdır,
bu yüzden koşturmuyoruz. Çözüm yayın bayrağında `release` ve `acquire` kullanmaktır.

#### seq_cst ile release/acquire Farkı

Yukarıdaki yayını sıralama belirtmeden yazsaydık, her işlem `seq_cst` olurdu. Sonuç
aynıdır: bayrağı acquire ile okuyan taraf yazılanları görür. Fark, `seq_cst`'in bir şey
daha eklemesidir: bütün `seq_cst` işlemleri için **tek bir toplam sıra** vardır ve her iş
parçacığı bu sıraya uyar. Release/acquire yalnızca bir yazı ile onu okuyan arasındaki
ilişkiyi kurar; iki ilgisiz bayrağın hangisinin önce yazıldığı konusunda iki gözlemcinin
anlaşmasını gerektirmez. İki bağımsız yazıcı ve iki okuyucu düşünün: okuyuculardan biri
`x`'in `y`'den önce yazıldığını, öbürü tersini görebilir. Release/acquire bunu
yasaklamaz; iki yazı ve iki okumanın hepsi `seq_cst` ise bu sonuç yasaktır. Bu durumu koşturmuyoruz, çünkü hangi sonucun görüleceği
çizelgeye ve donanıma bağlıdır; ders yalnızca garantinin kapsamını ayırır.

Bu ek garanti bir bedelle gelir: ortak toplam sırayı sağlamak bazı donanımlarda ek
engeller gerektirir. Sizin saydığınız eşgüdüm noktası ise değişmez, yine bir yazıdır.
Bu yüzden `seq_cst`'i bilerek seçmek yerine, yayının yalnızca bir yazı ile bir okumaya
dayandığını görüyorsanız release/acquire'a inmek bir seçimdir. Emin değilseniz
varsayılan olan `seq_cst` doğrudur; bu bir hız iddiası değil, doğruluk tercihidir.

```cpp
// siralama.hpp — ayni yayin, varsayilan seq_cst siralamayla
#pragma once
#include <atomic>
#include <thread>

#include "yayin.hpp"

struct VarsayilanYayin {
    int veri[ELEMAN];
    std::atomic<int> hazir{0};
    long esgudum = 0;
    long toplam = 0;
};

inline void uret_varsayilan(VarsayilanYayin& y) {
    for (int i = 0; i < ELEMAN; ++i) y.veri[i] = i;
    y.hazir.store(1);
    ++y.esgudum;
}

inline void tuket_varsayilan(VarsayilanYayin& y) {
    while (y.hazir.load() == 0) std::this_thread::yield();
    long t = 0;
    for (int i = 0; i < ELEMAN; ++i) t += y.veri[i];
    y.toplam = t;
}
```

#### Okuma-Değiştirme-Yazma ve compare_exchange

`fetch_add` değeri her zaman artırır. Bazen yazının **koşullu** olması gerekir: "değer hâlâ
okuduğum değerse yaz". `compare_exchange_strong(beklenen, istenen)` bunu tek bölünmez
işlemle yapar. Atomik değer `beklenen`'e eşitse `istenen` yazılır ve `true` döner; eşit
değilse hiçbir şey yazılmaz, `false` döner ve `beklenen` atomiğin **o anki** değeriyle
güncellenir. Dört iş parçacığı aynı bayrağı `0`'dan `1`'e çevirmeye çalışırsa tek biri
kazanır; diğerleri başarısızlığı görür. Kazananı seçmek için kilit gerekmez.

İşlemin `weak` biçimi, değer eşit olduğu halde de başarısız olabilir; bu yüzden hep bir
döngü içinde kullanılır. Karşılığında bazı donanımlarda daha ucuzdur. Tipik kalıp,
değeri okumak, bir aday hesaplamak ve `compare_exchange_weak` ile yazmayı denemektir;
başarısızlıkta `beklenen` zaten yeni değerle yenilendiği için döngü yeniden okumaz.
Aşağıdaki örnek, dört iş parçacığının gördüğü en büyük değeri kilitsiz bulur: aday
mevcut değerden büyükse yazmayı dener, değilse döngüden hemen çıkar.

```cpp
// rmw.hpp — compare_exchange ile tek kazanan ve en buyuk deger
#pragma once
#include <atomic>
#include <thread>
#include <vector>

inline int tek_kazanan(int is_sayisi) {
    std::atomic<int> bayrak{0};
    std::atomic<int> kazanan{0};
    std::vector<std::thread> isler;
    for (int t = 0; t < is_sayisi; ++t) {
        isler.emplace_back([&] {
            int beklenen = 0;
            if (bayrak.compare_exchange_strong(beklenen, 1)) kazanan.fetch_add(1);
        });
    }
    for (auto& is : isler) is.join();
    return kazanan.load();
}

inline int en_buyuk(int is_sayisi, int adet) {
    std::atomic<int> enbuyuk{0};
    std::vector<std::thread> isler;
    for (int t = 0; t < is_sayisi; ++t) {
        isler.emplace_back([&, t] {
            for (int k = 0; k < adet; ++k) {
                int aday = k * is_sayisi + t;
                int mevcut = enbuyuk.load(std::memory_order_relaxed);
                while (aday > mevcut && !enbuyuk.compare_exchange_weak(mevcut, aday)) {
                }
            }
        });
    }
    for (auto& is : isler) is.join();
    return enbuyuk.load();
}
```

Döngünün kaç kez döndüğü çizelgeye bağlıdır, bu yüzden basılmaz. Sonuç ise çizelgeden
bağımsızdır: en büyük aday her koşulda yazılır.

#### Önce-Olur Zincirini Adım Adım Okumak

Önce-olur bağıntısı **geçişlidir**. Üç iş parçacığı olsun: A bir düz değeri yazıp
`bayrak1`'e release ile yazar; B `bayrak1`'i acquire ile görür, kendi düz değerini
yazar ve `bayrak2`'ye release ile yazar; C `bayrak2`'yi acquire ile görür. C'nin
gördüğü, A'nın hiç dokunmadığı bir bayrak üzerinden gelir; yine de A'nın yazısını da
görür. Zinciri şöyle okuruz.

1. A'nın düz yazısı, A'nın `bayrak1` release yazısından önce olur (aynı iş parçacığında
   sıra).
2. `bayrak1` release yazısı, B'nin onu gören acquire okumasından önce olur
   (release/acquire çifti).
3. B'nin acquire okuması, B'nin düz yazısından ve `bayrak2` release yazısından önce
   olur (aynı iş parçacığında sıra).
4. `bayrak2` release yazısı, C'nin acquire okumasından önce olur (ikinci çift).
5. Geçişlilik: A'nın yazısı C'nin okumasından önce olur, B'ninki de.

Zincirdeki her halka ya aynı iş parçacığındaki sıradır ya da bir release/acquire
çiftidir. Bir halka koparsa, örneğin B bayrağı relaxed okursa, 2. adım kurulmaz ve C'nin
A'nın yazısını görmesinin hiçbir garantisi kalmaz.

```cpp
// zincir.hpp — uc is parcaciginda onceolur zinciri
#pragma once
#include <atomic>
#include <thread>

struct Zincir {
    int a = 0;
    int b = 0;
    std::atomic<int> bayrak1{0};
    std::atomic<int> bayrak2{0};
    int gorulen_a = 0;
    int gorulen_b = 0;
};

inline void zincir_a(Zincir& z) {
    z.a = 10;
    z.bayrak1.store(1, std::memory_order_release);
}

inline void zincir_b(Zincir& z) {
    while (z.bayrak1.load(std::memory_order_acquire) == 0) std::this_thread::yield();
    z.b = z.a + 5;
    z.bayrak2.store(1, std::memory_order_release);
}

inline void zincir_c(Zincir& z) {
    while (z.bayrak2.load(std::memory_order_acquire) == 0) std::this_thread::yield();
    z.gorulen_a = z.a;
    z.gorulen_b = z.b;
}
```

#### Ölçüm: Kaç Eşgüdüm Noktası

```cpp
// yayin_olcumu.cpp — iki yayin bicimi ve relaxed bir sayac
#include <atomic>
#include <functional>
#include <iostream>
#include <thread>
#include <vector>

#include "rmw.hpp"
#include "siralama.hpp"
#include "yayin.hpp"
#include "zincir.hpp"

int main() {
    TekBayrak tek;
    {
        std::thread uretici(uret_tek, std::ref(tek));
        std::thread tuketici(tuket_tek, std::ref(tek));
        uretici.join();
        tuketici.join();
    }
    std::cout << "release/acquire yayini bekleme esgudum=" << tek.esgudum
              << " toplam=" << tek.toplam << "\n";

    HerEleman her;
    {
        std::thread uretici(uret_hepsi, std::ref(her));
        std::thread tuketici(tuket_hepsi, std::ref(her));
        uretici.join();
        tuketici.join();
    }
    std::cout << "her eleman atomik bekleme esgudum=" << her.esgudum
              << " toplam=" << her.toplam << "\n";

    std::atomic<long> sayac{0};
    std::vector<std::thread> isler;
    for (int i = 0; i < 4; ++i) {
        isler.emplace_back([&sayac] {
            for (int k = 0; k < 10000; ++k) sayac.fetch_add(1, std::memory_order_relaxed);
        });
    }
    for (auto& is : isler) is.join();
    std::cout << "relaxed sayac bekleme deger=" << sayac.load() << "\n";

    VarsayilanYayin varsayilan;
    {
        std::thread uretici(uret_varsayilan, std::ref(varsayilan));
        std::thread tuketici(tuket_varsayilan, std::ref(varsayilan));
        uretici.join();
        tuketici.join();
    }
    std::cout << "seq_cst yayini bekleme esgudum=" << varsayilan.esgudum
              << " toplam=" << varsayilan.toplam << "\n";

    std::cout << "compare exchange bekleme kazanan=" << tek_kazanan(4) << "\n";
    std::cout << "cas dongusu bekleme enbuyuk=" << en_buyuk(4, 1000) << "\n";

    Zincir zincir;
    {
        std::thread c(zincir_c, std::ref(zincir));
        std::thread b(zincir_b, std::ref(zincir));
        std::thread a(zincir_a, std::ref(zincir));
        a.join();
        b.join();
        c.join();
    }
    std::cout << "onceolur zinciri bekleme a=" << zincir.gorulen_a
              << " b=" << zincir.gorulen_b << "\n";
    return 0;
}
```

```text
release/acquire yayini bekleme esgudum=1 toplam=499500
her eleman atomik bekleme esgudum=1000 toplam=499500
relaxed sayac bekleme deger=40000
seq_cst yayini bekleme esgudum=1 toplam=499500
compare exchange bekleme kazanan=1
cas dongusu bekleme enbuyuk=3999
onceolur zinciri bekleme a=10 b=15
```

**EB7.** İlk satır, bin düz değerin tek bir release yazısıyla yayınlandığını gösterir:
`esgudum=1`. Tüketici acquire okumasıyla bayrağı görünce bin elemanın tamamını okudu ve
toplam `499500` çıktı; bu, sıfırdan dokuz yüz doksan dokuza kadar sayıların toplamıdır.
Tüketicinin bayrağı görmeden önce kaç kez döndüğü basılmaz, çünkü bu sayı çizelgeye
bağlıdır; ölçülen şey döngü sayısı değil, yazılan release sayısıdır.

**EB8.** Üçüncü satır, relaxed `fetch_add` sayacının yine `40000` verdiğini gösterir.
Sıralama gevşek olduğu halde sonuç doğrudur; çünkü bölünmezlik sıralama değildir ve her
artış başka bir artışın içine girmez. Relaxed sayaç, yalnızca son değerin okunduğu ve
başka veriyi yayınlamadığı durumda doğru kullanımdır.

**EB9.** Dördüncü satır, aynı yayının varsayılan `seq_cst` sıralamayla da bir yazı ve
bir okumadan oluştuğunu gösterir: `esgudum=1`, `toplam=499500`. Eşgüdüm noktası sayısı
sıralamayı değiştirmekle değişmedi; `seq_cst` farkı bu satırdan okunmaz, çünkü fark yalnızca
birbirinden bağımsız iki yayının gözlemciler tarafından sıralanışında ortaya çıkar.

**EB10.** `compare_exchange_strong` ile dört iş parçacığı aynı bayrağı `0`'dan `1`'e
çevirmeye çalıştı ve `kazanan=1` çıktı. Dördünün de aynı anda başarılı olması mümkün
olsaydı sayı birden büyük çıkardı; bölünmez karşılaştırma ve yazma bunu önledi. Hangi iş
parçacığının kazandığı basılmadı, çünkü çizelgeye bağlıdır.

**EB11.** `enbuyuk=3999` değeri, dört iş parçacığının ürettiği adayların en büyüğüdür:
son adım `k=999` ve son iş parçacığı `t=3` için `999 * 4 + 3`. Başka bir iş parçacığı
araya girip değeri değiştirdiğinde `compare_exchange_weak` başarısız oldu ve `mevcut`'u
güncelledi; döngü adayı yeni değerle yeniden karşılaştırdı. Hiçbir büyük değer, küçük
bir değerin ardından yazılıp kaybolmadı.

**EB12.** Son satırda C iş parçacığı hem `a=10` hem `b=15` değerini gördü. C'nin hiç
okumadığı `bayrak1` üzerinden gelen `a`, zincirin geçişliliği sayesinde görünür oldu.
İş parçacıkları sıra dışı başlatılsa bile (C ilk açıldı) sonuç değişmedi, çünkü C
bayrağı görene kadar döngüde kaldı.

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

Bu dersin ölçtüğü düzenek, bellek sıralamasının eşgüdüm noktası sayısını belirlemesidir.
İkinci satır, aynı bin değeri bu kez her elemanı ayrı bir atomik yaparak yayınlıyor.
Her yazı release, her okuma acquire; doğruluk bozulmadı, `toplam=499500`. Ama
`esgudum=1000`: bin kat fazla eşgüdüm noktası.

Bu fark verinin kendisinden değil, **yayının ne kadar ince bölündüğünden** gelir. Bir
bayrak bin değeri korurken, bin atomik her değeri tek tek korur. Önce-olur bağıntısı
tek bir release-acquire çifti için ucuzdur ve çiftin önündeki bütün yazıları kapsar;
bunun için yayınlanacak veriyi önce toplamak, sonra tek bir noktadan açmak gerekir.
Sınır şudur: tüketici verinin yarısını beklemeden kullanmak zorundaysa, tek bayrak
yetmez ve parçalı yayın kaçınılmaz olur. Bedeli düşürmek için sıralamayı gevşetmek
yetmez; yayının kaç noktaya bölündüğünü azaltmak gerekir.

#### Özet

- Bu dersin ölçtüğü düzenek bedeli **bekleme** sınıfına taşır: bellek sıralaması, bir
  veri yayınının kaç eşgüdüm noktası istediğini belirler; ölçü gerçek bekleme değil, bu
  noktaların sayısıdır.
- `std::atomic` bölünmezlik verir; `fetch_add` gibi okuma-değiştirme-yazma işlemleri üç
  adımı tek işleme çevirir. Bölünmezlik, sıralama değildir.
- Release yazısı ile acquire okuması önce-olur bağıntısını kurar: acquire, release'in
  yazdığı değeri okuduğunda release'ten önceki bütün yazılar acquire'dan sonraki bütün
  okumalara görünür olur.
- `seq_cst` varsayılandır ve bütün `seq_cst` işlemlerini tek bir ortak sırada görünür kılar;
  `relaxed` yalnızca bölünmezlik verir, bir yayın bayrağı olarak kullanılması veri
  yarışıdır.
- `release/acquire yayini` bin değeri bir noktada, `her eleman atomik` düzeneği bin
  noktada yayınladı; ikisi de aynı toplamı verdi.

#### Sonraki Adım

Eşgüdüm noktalarını azaltmak, işin yarısıdır; kalan yarısı verinin bellekte nasıl
dizildiğidir. Bir sonraki ders önbellek satırını ve yerelliği açıyor: bir veri düzeni
kaç geçişi atlatıyor sorusuyla yapı dizisi ile dizi yapısını karşılaştıracağız.

### Önbellek Dostu Veri Düzeni

Source: https://academia.sh/tr/kurslar/cpp-sablonlar/onbellek-dostu-veri-duzeni

Önbellek satırı, uzamsal ve zamansal yerellik, yapı dizisi ile dizi yapısı, satır ve sütun sırası, sahte paylaşım ve bir veri düzeninin kaç bitişik olmayan geçiş bıraktığının sayılması.

Önceki iki ders eşzamanlılığın bedelini ölçtü: kilit kaç kez alınıyor, bir yayın kaç
eşgüdüm noktası istiyor. Bu ders tek bir iş parçacığına dönüyor, çünkü bellekte verinin
nasıl dizildiği eşzamanlılık olmadan da bedeli değiştirir. İşlemci belleği tek tek bayt
olarak değil, **önbellek satırı** denen sabit boyutlu bloklar halinde taşır. Bir değere
dokunduğunuzda onunla birlikte çevresindeki komşu baytlar da yakın belleğe gelir.
Komşularının bir kısmına yakında dokunacaksanız bu iyi bir takastır; dokunmayacaksanız
taşınan baytların çoğu boşa gitmiştir. Kursun ölçtüğü soru yine aynı: düzenek bedeli
nereye taşıyor? Bu dersin cevabı **bellek** sınıfıdır. Satır boyunu ve adresleri
basmıyoruz, çünkü ikisi de makineye bağlıdır; ölçü, bir döngünün art arda dokunduğu iki
değerin bellekte yan yana olup olmadığını sayar.

#### Yerellik

İki tür yerellik vardır. **Uzamsal yerellik**, bir değere dokunan programın yakında
onun komşusuna da dokunacağı beklentisidir; dizinin elemanlarını sırayla gezmek bunun
örneğidir. **Zamansal yerellik**, bir değere dokunan programın yakında aynı değere
yeniden dokunacağı beklentisidir; bir döngü değişkeni ya da bir toplam bunun örneğidir.
Önbellek satırı, uzamsal yerelliği ödüllendirir: satırda yan yana duran değerler tek bir
taşımayla gelir.

Çok boyutlu dizilerde aynı fikir satır ve sütun sırası olarak görünür. C++'ta
`int tablo[satir][sutun]` satır sırasındadır: aynı satırın elemanları bitişiktir.
Satırlar boyunca gezen iç döngü uzamsal yerelliğe uyar; sütunlar boyunca gezen iç döngü
her adımda bir satır boyu atlar. Bu ayrımı aşağıda geçiş sayımıyla gösteriyoruz.

#### Yapı Dizisi ve Dizi Yapısı

Bir nokta kümesi üç alanla `x`, `y` ve `z` tutulur. İlk düzen **yapı dizisidir (AoS)**:
her nokta bir yapıdır ve bütün noktalar bir dizide durur. Bellekte sıra
`x0 y0 z0 x1 y1 z1 ...` olur. İkinci düzen **dizi yapısıdır (SoA)**: üç alanın her biri
kendi dizisinde durur. Bellekte sıra `x0 x1 x2 ... y0 y1 y2 ... z0 z1 z2 ...` olur.

Yalnız `x` alanını dolaşan bir döngü, yapı dizisinde her adımda diğer iki alanı da
atlar; bitişik olan `x` değerleri değil, aralarında `y` ve `z` olan değerlerdir. Dizi
yapısında `x` değerleri yan yanadır. Aşağıdaki başlık iki düzeni ve ölçümü kurar. Ölçü,
bir döngünün dokunduğu adresleri sırayla kaydeder ve art arda iki dokunuşun arasındaki
mesafenin tam bir `int` kadar olup olmadığına bakar. Adres basılmaz; yalnızca sayı çıkar.

```cpp
// dizilim.hpp — iki veri duzeni ve dokunuslari kaydeden olcum
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>

constexpr int NOKTA_SAYISI = 4;

struct Nokta {
    int x, y, z;
};

struct NoktaDizisi {
    std::array<Nokta, NOKTA_SAYISI> noktalar;
};

struct AlanDizileri {
    std::array<int, NOKTA_SAYISI> x, y, z;
};

inline NoktaDizisi yapi_dizisi_kur() {
    NoktaDizisi d;
    for (int i = 0; i < NOKTA_SAYISI; ++i) d.noktalar[i] = Nokta{i, 10 + i, 20 + i};
    return d;
}

inline AlanDizileri dizi_yapisi_kur() {
    AlanDizileri d;
    for (int i = 0; i < NOKTA_SAYISI; ++i) {
        d.x[i] = i;
        d.y[i] = 10 + i;
        d.z[i] = 20 + i;
    }
    return d;
}

inline bool bitisik(const int* once, const int* sonra) {
    auto a = reinterpret_cast<std::uintptr_t>(once);
    auto b = reinterpret_cast<std::uintptr_t>(sonra);
    return b - a == sizeof(int);
}

struct Gecisler {
    int atlanan = 0;
    int toplam = 0;
};

inline Gecisler say(const std::vector<const int*>& dokunuslar) {
    Gecisler g;
    for (std::size_t i = 0; i + 1 < dokunuslar.size(); ++i) {
        ++g.toplam;
        if (!bitisik(dokunuslar[i], dokunuslar[i + 1])) ++g.atlanan;
    }
    return g;
}
```

Bitişiklik testi işaretçileri tam sayıya çevirip farkı `sizeof(int)` ile karşılaştırır.
İki ayrı dizinin işaretçilerini çıkarmak tanımsız olurdu; tam sayıya çevirmek bu yüzden
gereklidir. `atlanan` bitişik olmayan geçişleri, `toplam` bütün geçişleri sayar.

#### Satır Sırası ve Sütun Sırası: Geçiş Sayımı

Aynı fikri tek bir düz dizi üzerinde sayıya dökelim. Dört satır ve dört sütunluk bir
tablo, on altı elemanlı tek bir dizide satır sırasıyla durur: `m[r * 4 + c]` ifadesi
`r`. satırın `c`. elemanıdır. İki iç içe döngüyü ele alalım. Birincisinde dış döngü
satırlar, iç döngü sütunlar üzerinde döner; ardışık dokunuşlar dizide yan yanadır.
İkincisinde dış döngü sütunlar, iç döngü satırlar üzerinde döner; ardışık dokunuşlar
her adımda bir satır boyu, yani dört `int` atlar, sütun değişirken de geriye döner.

Dikkat edilecek nokta, on altı dokunuşun on beş **geçiş** ürettiğidir: geçiş, art arda
iki dokunuş arasındaki adımdır. Satır sırasında on beş geçişin hiçbiri atlamaz. Sütun
sırasında on beşinin hepsi atlar, çünkü ardışık iki dokunuş ya dört `int` ileridedir ya
da geridedir. Geriye atlayan geçiş de bitişik sayılmaz: bitişiklik testi farkın tam bir
`int` olmasını ister.

```cpp
// matris.hpp — dort satir dort sutunluk duz dizide iki dolasma sirasi
#pragma once
#include <array>

#include "dizilim.hpp"

constexpr int SATIR = 4;
constexpr int SUTUN = 4;

inline Gecisler satir_sirasi_say(const std::array<int, SATIR * SUTUN>& m) {
    std::vector<const int*> dokunuslar;
    for (int r = 0; r < SATIR; ++r)
        for (int c = 0; c < SUTUN; ++c) dokunuslar.push_back(&m[r * SUTUN + c]);
    return say(dokunuslar);
}

inline Gecisler sutun_sirasi_say(const std::array<int, SATIR * SUTUN>& m) {
    std::vector<const int*> dokunuslar;
    for (int c = 0; c < SUTUN; ++c)
        for (int r = 0; r < SATIR; ++r) dokunuslar.push_back(&m[r * SUTUN + c]);
    return say(dokunuslar);
}
```

Bu, bir önceki bölümdeki aynı `say` işlevidir; yeni bir ölçü yok, yalnızca iki yeni
erişim örüntüsü var. Gerçek bir tabloda bu fark, her atlayan geçişin başka bir önbellek
satırını getirme ihtimaline dönüşür. Tablonun bir satırı önbellek satırından uzunsa
atlayan her geçiş başka bir önbellek satırına düşer, bu yüzden büyük tablolarda döngü sırasını verinin dizilişine uydurmak ilk bakılacak yerdir.

#### Sahte Paylaşım ve alignas

Veri düzeni yalnızca tek iş parçacığını ilgilendirmez. İki iş parçacığı birbirinden
bağımsız iki değişkene yazıyorsa ama bu iki değişken aynı önbellek satırına düşmüşse,
bir iş parçacığının yazısı öbürünün satırını geçersiz kılar ve iki iş parçacığı
birbirinin önbelleğini sürekli boşaltır. Buna **sahte paylaşım** denir: değişkenler
paylaşılmıyor, ama satır paylaşılıyor. Kod doğrudur, veri yarışı yoktur; yalnızca
her yazı satırın sahipliğini el değiştirmeye zorlar. Bu, kilitlerin ya da atomiklerin
dersindeki bekleme noktalarından farklı bir bedeldir: kodda hiçbir eşgüdüm görünmez, bedel
düzenden gelir.

Çözüm, iş parçacıklarının yazdığı alanları farklı satırlara ayırmaktır. C++'ta bunu
`alignas` belirteci yapar: bir tipe `alignas(N)` verilirse o tipin nesnelerinin adresi `N`'in katı olur
ve tipin boyutu da `N`'in katına yuvarlanır. İki ardışık `Hizali` elemanı bu yüzden en
az `N` bayt arayla durur.

Burada `64` sayısı bir **varsayımdır**: yaygın işlemcilerde önbellek satırı bu boydadır,
ama dilin söylediği bir sabit değildir ve her donanımda doğru olmayabilir. Bu yüzden
sayıyı adlandırıp tek yerde tutuyoruz ve ölçüm, gerçek satırı değil, o varsayımı
kullanıyor. Ayrıca aşağıdaki `static_assert`, hizalamanın gerçekten istenen değerde
olduğunu derleme sırasında doğrular. Sayım, ardışık iki elemanın adresleri arasındaki
farkın varsayılan satır boyundan küçük olup olmadığına bakar; küçükse ikisi aynı satıra
düşme **adayıdır**.

```cpp
// paylasim.hpp — paketli ve hizali sayaclar, ayni satira dusme adayi sayimi
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <thread>
#include <vector>

constexpr std::size_t SATIR_VARSAYIM = 64;  // varsayim: yaygin islemci onbellek satiri

struct Paketli {
    long deger = 0;
};

struct alignas(SATIR_VARSAYIM) Hizali {
    long deger = 0;
};

static_assert(alignof(Hizali) == SATIR_VARSAYIM, "hizalama istenen degerde degil");
static_assert(sizeof(Hizali) % SATIR_VARSAYIM == 0, "boyut satira yuvarlanmadi");

template <typename T>
int ayni_satir_adayi(const std::array<T, 4>& dizi) {
    int aday = 0;
    for (std::size_t i = 0; i + 1 < dizi.size(); ++i) {
        auto a = reinterpret_cast<std::uintptr_t>(&dizi[i]);
        auto b = reinterpret_cast<std::uintptr_t>(&dizi[i + 1]);
        if (b - a < SATIR_VARSAYIM) ++aday;
    }
    return aday;
}

template <typename T>
long dort_yazici(std::array<T, 4>& dizi) {
    std::vector<std::thread> isler;
    for (int t = 0; t < 4; ++t) {
        isler.emplace_back([&dizi, t] {
            for (int k = 0; k < 10000; ++k) ++dizi[t].deger;
        });
    }
    for (auto& is : isler) is.join();
    long toplam = 0;
    for (const auto& e : dizi) toplam += e.deger;
    return toplam;
}
```

Her iş parçacığı yalnızca kendi elemanına yazar; bu yüzden iki sürüm de doğrudur ve aynı
toplamı verir. `ayni_satir_adayi` bu platformda sekiz baytlık olan `long` elemanlar için
üç çiftin üçünü, altmış dört baytlık hizalı elemanlar için üçünün hiçbirini aday sayar. Burada sayılan şey
gerçek bir taşıma değil, düzenin **yol açabileceği** durumdur.

#### Ölçüm: Yalnız x Alanı, Tablo ve Sayaçlar

Aşağıdaki program üç ölçümü birlikte koşturur ve her birinin sayacını basar.

```cpp
// dizilim_olcumu.cpp — yalniz x alani ve uc alan birlikte
#include <array>
#include <iostream>
#include <vector>

#include "dizilim.hpp"
#include "matris.hpp"
#include "paylasim.hpp"

int main() {
    NoktaDizisi aos = yapi_dizisi_kur();
    AlanDizileri soa = dizi_yapisi_kur();

    std::vector<const int*> aos_x;
    std::vector<const int*> soa_x;
    int aos_toplam = 0;
    int soa_toplam = 0;
    for (int i = 0; i < NOKTA_SAYISI; ++i) {
        aos_x.push_back(&aos.noktalar[i].x);
        aos_toplam += aos.noktalar[i].x;
        soa_x.push_back(&soa.x[i]);
        soa_toplam += soa.x[i];
    }
    Gecisler a = say(aos_x);
    Gecisler s = say(soa_x);
    std::cout << "yalniz x alani bellek aos=" << a.atlanan << "/" << a.toplam
              << " soa=" << s.atlanan << "/" << s.toplam << "\n";
    std::cout << "toplam x aos=" << aos_toplam << " soa=" << soa_toplam << "\n";

    std::array<int, SATIR * SUTUN> tablo{};
    for (int i = 0; i < SATIR * SUTUN; ++i) tablo[i] = i;
    Gecisler satir = satir_sirasi_say(tablo);
    Gecisler sutun = sutun_sirasi_say(tablo);
    std::cout << "satir ve sutun sirasi bellek satir=" << satir.atlanan << "/"
              << satir.toplam << " sutun=" << sutun.atlanan << "/" << sutun.toplam << "\n";

    std::array<Paketli, 4> paketli{};
    std::array<Hizali, 4> hizali{};
    std::cout << "sahte paylasim bellek paketli=" << ayni_satir_adayi(paketli) << "/3"
              << " hizali=" << ayni_satir_adayi(hizali) << "/3\n";
    long paketli_toplam = dort_yazici(paketli);
    long hizali_toplam = dort_yazici(hizali);
    std::cout << "sahte paylasim sonuc paketli=" << paketli_toplam
              << " hizali=" << hizali_toplam << "\n";

    std::vector<const int*> aos_uc;
    std::vector<const int*> soa_uc;
    for (int i = 0; i < NOKTA_SAYISI; ++i) {
        aos_uc.push_back(&aos.noktalar[i].x);
        aos_uc.push_back(&aos.noktalar[i].y);
        aos_uc.push_back(&aos.noktalar[i].z);
        soa_uc.push_back(&soa.x[i]);
        soa_uc.push_back(&soa.y[i]);
        soa_uc.push_back(&soa.z[i]);
    }
    Gecisler au = say(aos_uc);
    Gecisler su = say(soa_uc);
    std::cout << "uc alan birlikte bellek aos=" << au.atlanan << "/" << au.toplam
              << " soa=" << su.atlanan << "/" << su.toplam << "\n";
    return 0;
}
```

```text
yalniz x alani bellek aos=3/3 soa=0/3
toplam x aos=6 soa=6
satir ve sutun sirasi bellek satir=0/15 sutun=15/15
sahte paylasim bellek paketli=3/3 hizali=0/3
sahte paylasim sonuc paketli=40000 hizali=40000
uc alan birlikte bellek aos=0/11 soa=11/11
```

**EB13.** İlk satır yalnız `x` alanını dolaşan döngünün iki düzende dört elemanda üç
geçiş yaptığını, yapı dizisinde üçünün de bitişik olmadığını, dizi yapısında hiçbirinin
atlamadığını gösterir: `aos=3/3`, `soa=0/3`. İkinci satır iki düzenin aynı toplamı
verdiğini doğrular, yani fark sonuçta değil dokunuşların bellekteki konumundadır.

**EB14.** Sayım yalnızca bitişik olmayan geçişleri veriyor, satır boyunu vermiyor: dört
noktalık küçük bir küme tek bir önbellek satırına sığabilir ve atlanan her geçiş yeni
bir satır taşımasına yol açmayabilir. Bu yüzden `3/3` bir taşıma sayısı değil, düzenin
bitişikliği bozma **yönüdür**; gerçek taşıma sayısı küme büyüdükçe anlam kazanır ve
makineye bağlıdır.

**EB15.** Üçüncü satır, dört satır dört sütunluk tabloda satır sırasıyla dolaşmanın on
beş geçişin hiçbirini atlatmadığını (`satir=0/15`), sütun sırasıyla dolaşmanın ise
hepsini atlattığını (`sutun=15/15`) gösterir. İki döngü aynı on altı elemana dokundu,
yalnızca sıra farklıydı. Sütun sırasında geriye dönen geçişler de sayıya girdi, çünkü
bitişiklik testi yalnızca ileri doğru tam bir `int` adımı kabul eder.

**EB16.** Dördüncü satırda bu platformda sekiz baytlık olan `long` elemanlardan oluşan paketli dizi, üç
ardışık çiftin üçünü de aynı satıra düşme adayı yaptı (`paketli=3/3`); `alignas`
verilen dizi hiçbirini yapmadı (`hizali=0/3`). Bu sayı gerçek bir önbellek satırı
ölçümü değildir, `SATIR_VARSAYIM` varsayımına dayanır. Varsayım yanlışsa sayım da
yanlış olur; bu yüzden değeri adlandırdık ve `static_assert` ile hizalamanın istenen
değerde olduğunu doğruladık.

**EB17.** Beşinci satırda iki sürüm de `40000` verdi. Her iş parçacığı yalnızca kendi
elemanına yazdığı için veri yarışı yoktu ve yerleşim sonucu değiştirmedi. `alignas`
doğruluğu değiştirmez, bedelin nerede ödendiğini değiştirir; bu değişiklik
çıktıda görünmez ve bu derste bilinçli olarak ölçülmeyen kısımdır.

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

Bu dersin ölçtüğü düzen, yalnız bir alana dokunan döngüde dizi yapısının üstünlüğüdür.
Son satır aynı iki düzeni bu kez üç alanı birlikte okuyan bir döngüyle ölçer: her
nokta için `x`, `y`, `z` sırayla okunur. Burada tablo tersine döner: yapı dizisinde bir
noktanın üç alanı yan yanadır ve bir sonraki noktanın alanları da öyle, yani on bir
geçişin hiçbiri atlanmaz: `aos=0/11`. Dizi yapısında her okuma farklı bir diziye
atlar: `soa=11/11`.

Düzenin bitişikliğini veri belirlemiyor, **erişim örüntüsü** belirliyor. Aynı veri bir
örüntüde en iyi, bir başkasında en kötü düzende durabilir. Bu yüzden "dizi yapısı daha
hızlıdır" ya da "yapı dizisi daha doğaldır" diye bir kural yoktur. Soru, döngünün hangi
alanlara birlikte dokunduğudur; alanlar birlikte okunuyorsa yapı dizisi, ayrı ayrı
okunuyorsa dizi yapısı bitişikliği korur.

#### Özet

- Bu dersin ölçtüğü düzenek bedeli **bellek** sınıfına taşır: bir veri düzeni, bir
  döngünün art arda dokunduğu değerlerin ne kadarını bitişik bıraktığını belirler; ölçü
  süre değil, bitişik olmayan geçiş sayısıdır.
- Önbellek satırı belleği bloklar halinde taşır; uzamsal yerellik komşuya, zamansal
  yerellik aynı değere yeniden dokunmayı ödüllendirir.
- Yapı dizisi alanları birlikte, dizi yapısı alanları ayrı ayrı bitişik tutar; hangisinin
  uygun olduğunu erişim örüntüsü belirler.
- `yalniz x alani` yapı dizisinde `aos=3/3`, dizi yapısında `soa=0/3` bıraktı; `uc alan
  birlikte` aynı iki düzende `aos=0/11` ve `soa=11/11` bıraktı.
- Satır sırasıyla dolaşmak `satir=0/15`, sütun sırasıyla dolaşmak `sutun=15/15` atlama
  bıraktı; sıra, verinin dizilişine uymalıdır.
- Sahte paylaşım, bağımsız değişkenlerin aynı satıra düşmesidir; çözümü iş parçacığı
  başına satır ayırmaktır, `alignas` ile; satır boyu burada bir varsayımdır.

#### Sonraki Adım

Bu derste hangi düzenin hangi örüntüye uyduğunu bildiğimizi varsaydık, ama gerçek bir programda hangi
döngünün sıcak olduğunu bilmeden hangi düzene dokunacağımızı bilemeyiz. Bir sonraki ders
profillemeyi açıyor: bir sayaç sıcak noktayı nerede buluyor sorusuyla araçlı bir
profilleyici kuracağız.

### Profilleme ve Karşılaştırmalı Ölçüm

Source: https://academia.sh/tr/kurslar/cpp-sablonlar/profilleme-ve-karsilastirmali-olcum

Enstrümantasyon ile örnekleme, sayaç yerleştirerek sıcak noktayı bulma, karşılaştırmalı ölçümün ısınma, yineleme ve ölü kod kuralları ve ölçümün nerede başladığının sonucu nasıl değiştirdiği.

Önceki ders veri düzeninin bir döngünün bellek bedelini nasıl değiştirdiğini gösterdi,
ama ders bir şeyi varsaydı: hangi döngünün önemli olduğunu önceden biliyorduk. Gerçek bir
programda bu bilgi yoktur. Eniyilenecek yeri tahminle seçmek, önemli olmayan bir yere
emek harcamanın en kısa yoludur. Bu ders, hangi yerin sıcak olduğunu **ölçerek** bulmayı
anlatıyor. Kursun ölçtüğü soru yine aynı: düzenek bedeli nereye taşıyor? Cevabın sınıfı
**koşum**dur: profilleme koşarken ödenen bir düzenektir ve sonucunu koşan program verir.
M10/K03'te "kaç kez koştu" sorusunda sayı ölçünün kendisiydi; burada sayı kanıt aracıdır
ve ders sınıf adıyla cevaplanır. Süre basılmaz: süre makineye ve yüke bağlıdır, sıcak nokta
ise sayaçtan bulunur.

#### İki Yöntem: Enstrümantasyon ve Örnekleme

Bir profilleyici iki yoldan veri toplar. **Enstrümantasyon**, programın kendisine sayaç
yerleştirmektir: her işlev girişinde ya da her döngü adımında bir sayaç artırılır, program
bitince sayaçlar okunur. Sayım kesindir, çünkü her olay sayılır; ama sayaçlar koda dokunur,
ölçülen program artık ölçülmemiş programdan farklıdır.

**Örnekleme** ise programın kodunu değiştirmez. Profilleyici programı belirli aralıklarla durdurur ve
o anda hangi işlevin koştuğuna bakar; yeterince çok bakışta en sık görülen işlev sıcak
noktadır. Örneklemenin yükü daha küçüktür ama sonucu yaklaşıktır ve zamanlamaya bağlıdır;
aynı program iki koşuda biraz farklı dağılım verir. Bu derste örneklemeyi koşturmuyoruz,
çünkü çıktısı koşuma bağlıdır; enstrümantasyonla çalışıyoruz, çünkü çıktısı sayıdır ve
her koşuda aynıdır.

#### Örnekleme Bir İz Üzerinde

Örneklemenin modelini koşuma bağlamadan kurabiliriz. Programın her adımında hangi
işlevin koştuğunu bir **iz** olarak düşünelim: iz, adım sırasıyla işlev kimliklerinin
dizisidir. Örnekleyici bu izin her `periyot`. adımına bakar ve bulduğu kimliği sayar. Gerçek
bir örnekleyici izi görmez, durdurduğu anda yalnızca o anki işlevi görür; burada iz
önceden bilindiği için örneklemenin ne bulacağını hesaplayabiliriz. İz, yüz adım
`hazirla` ve ardından 4950 adım `ara` koşan bir programa karşılık gelir: toplam 5050 adım.

Örnekleme iki şey yapar. Sıcak noktayı bulur, çünkü uzun süren işlev daha çok bakışa
denk gelir. Ama sayımı kesin vermez: bakışlar izin belirli noktalarına düşer ve bu noktalar
**kaydırmaya** bağlıdır. Aynı izi sıfırıncı adımdan başlayarak ya da otuz beşinci adımdan
başlayarak örneklersek farklı sayılar elde ederiz. Sıcak nokta değişmez, ama kısa süren
işlevin payı oynar.

```cpp
// ornek.hpp — bir iz uzerinde periyodik ornekleme modeli
#pragma once
#include <array>
#include <cstddef>
#include <vector>

constexpr int ISLEV_HAZIRLA = 0;
constexpr int ISLEV_ARA = 1;

struct OrnekSayim {
    std::array<int, 2> adet{};
};

inline std::vector<int> iz_kur() {
    std::vector<int> iz;
    for (int i = 0; i < 100; ++i) iz.push_back(ISLEV_HAZIRLA);
    for (int i = 0; i < 4950; ++i) iz.push_back(ISLEV_ARA);
    return iz;
}

inline OrnekSayim ornekle(const std::vector<int>& iz, std::size_t periyot, std::size_t kaydirma) {
    OrnekSayim sayim;
    for (std::size_t i = kaydirma; i < iz.size(); i += periyot) ++sayim.adet[iz[i]];
    return sayim;
}
```

Periyot yetmiş seçildi. Bu sayı bir model parametresidir; gerçek bir örnekleyicinin
aralığı zamanla ölçülür ve burada karşılığı yoktur. Kaydırma sıfır olduğunda bakışlar
0, 70, 140, … adımlarına düşer; yüz adımlık `hazirla` bölgesine iki bakış girer. Kaydırma
otuz beş olduğunda bakışlar 35, 105, … adımlarına düşer ve bölgeye yalnızca biri girer.
Sayıyı basan program yalnızca bu hesabı koşturur; hesabın sonucu her koşuda aynıdır, çünkü
iz sabittir ve gerçek örneklemenin zamanlama gürültüsü modelde yoktur.

#### Sayaç Yerleştirmek

Aşağıdaki program yüz değerin içinde aynı olan çiftleri bulur. Üç işlem sayılır:
`hazirla`, bir değerin üretilmesi; `ara`, iki değerin karşılaştırılması; `ikiz`, eşit
çift bulunması. Sayaçlar bir `Profil` yapısında durur ve işlevlere parametre olarak
geçilir; sonuç yalnızca program bitince okunur.

```cpp
// profil.hpp — sayaclar ve profillenen islemler
#pragma once
#include <cstddef>
#include <vector>

struct Profil {
    long hazirla = 0;
    long ara = 0;
    long ikiz = 0;
};

inline int deger_hazirla(Profil& p, int i) {
    ++p.hazirla;
    return i % 50;
}

inline int ikizleri_say(const std::vector<int>& v, Profil& p) {
    int bulunan = 0;
    for (std::size_t i = 0; i < v.size(); ++i) {
        for (std::size_t j = i + 1; j < v.size(); ++j) {
            ++p.ara;
            if (v[i] == v[j]) {
                ++p.ikiz;
                ++bulunan;
            }
        }
    }
    return bulunan;
}
```

`deger_hazirla` `i % 50` döndürür; yüz değer üretilince her değer iki kez bulunur. Bu yüzden
elli çift beklenir. `ikizleri_say` iç içe iki döngüyle her çifti bir kez karşılaştırır;
yüz değerde çift sayısı `100 * 99 / 2 = 4950` olur.

#### Sıcak Noktayı Eniyilemek: Önce ve Sonra

Sayaç `ara` işlemini gösterdi, ama bu yalnızca bir teşhistir. Teşhisin değeri, ardından
yapılan değişikliği ölçebilmesidir. Eşit çiftleri bulmak için her çifti karşılaştırmak
gerekmez: değerlerin kaç kez geçtiğini bir frekans tablosunda saymak ve her değer için
`c * (c - 1) / 2` hesaplamak aynı sayıyı verir. Yüz değeri bir kez gezmek yüz adımdır;
elli kovayı bir kez gezmek elli adım daha ekler.

Karşılaştırma adil olsun diye iki yazım aynı sayaca, aynı girdiye ve aynı sonuç tipine
sahiptir. `ara` burada "ana döngünün adımı" anlamına gelir: eski yazımda bir
karşılaştırma, yenisinde bir değer okuma ya da bir kova toplama. İki yazımın adımları
aynı cins işler değildir; bu yüzden sayı iki yazımın **işlem sayısını** karşılaştırır,
süresini değil.

```cpp
// eniyi.hpp — frekans tablosuyla ayni sonucu veren ikinci yazim
#pragma once
#include <algorithm>
#include <vector>

#include "profil.hpp"

inline int ikizleri_say_tablo(const std::vector<int>& v, Profil& p) {
    int en_buyuk = *std::max_element(v.begin(), v.end());
    std::vector<int> sayim(static_cast<std::size_t>(en_buyuk) + 1, 0);
    for (int x : v) {
        ++sayim[static_cast<std::size_t>(x)];
        ++p.ara;
    }
    int bulunan = 0;
    for (int c : sayim) {
        bulunan += c * (c - 1) / 2;
        ++p.ara;
    }
    return bulunan;
}
```

Yeni yazım yüz değeri bir kez ve elli kovayı bir kez gezer: `150` adım. Eski yazım
`4950` adım atıyordu. İki yazım da elli çift buluyor; eniyileme sonucu değiştirmedi,
yalnızca sıcak noktanın boyutunu küçülttü. Sayı iki yazımın işlem sayısını karşılaştırır.
Sürenin düşüp düşmediği ise başka bir sorudur ve bu derste cevaplanmaz.

#### Karşılaştırmalı Ölçümün Kuralları

Profilleme bir programın içinde nerenin sıcak olduğunu söyler. **Karşılaştırmalı ölçüm**
ise iki yazımı aynı soruya karşı yarıştırır. Süreyle yapıldığında dört kural gerekir ve
hiçbiri kendiliğinden sağlanmaz. Birincisi **ısınma**: ilk koşu, önbellekleri ve tembel
kurulan yapıları doldurduğu için sonraki koşulardan farklıdır; ilk koşu atılmalı ya da
ayrıca raporlanmalıdır. İkincisi **yineleme**: tek bir koşu, o anki yüke bağlıdır; birçok
koşunun dağılımına bakılmalıdır. Üçüncüsü **aynı girdi**: iki yazım farklı girdilerle
ölçülürse fark girdiden de gelebilir. Dördüncüsü **ölü kod silme**: standart, gözlenebilir
davranışı değiştirmeyen hesabın derleyici tarafından silinmesine izin verir; sonucu hiçbir
yerde kullanılmayan bir hesap ölçülürken hiç koşmamış olabilir. Çözüm sonucu kullanmaktır:
bir yere yazmak ya da basmak.

Bu derste süre basmıyoruz, ama dört kural sayaçla ölçerken de geçerlidir. İlk üçü aşağıda
ayrı bir ölçüm programında uygulanır. Dördüncüsü için: aşağıdaki `ikizleri_say` işlevinin sonucu `bulunan` olarak
döndürülüp basılır, böylece hesap silinemez.

#### Ölçümün Nerede Başladığı

Isınma kuralı, ölçümün **nerede başladığının** sonucu değiştirebileceğini söyler. Bunu
sayaçla da gösterebiliriz. Bir tablo ilk sorguda kurulur, sonraki sorgularda hazır bulunur.
Kurulum yüz adım, her sorgu on adımdır; ilk sorguyu ölçüme dahil etmek ya da etmemek
sonucu değiştirir.

```cpp
// tablo.hpp — ilk cagrida kurulan tablo ve sorgu adimlari
#pragma once
#include <vector>

struct Tablo {
    std::vector<int> deger;
    bool hazir = false;
};

inline long sorgula(Tablo& t, int bas) {
    long adim = 0;
    if (!t.hazir) {
        for (int i = 0; i < 100; ++i) {
            t.deger.push_back(i * i);
            ++adim;
        }
        t.hazir = true;
    }
    long toplam = 0;
    for (int k = 0; k < 10; ++k) {
        toplam += t.deger[bas + k];
        ++adim;
    }
    (void)toplam;
    return adim;
}
```

`adim` yerel bir sayaçtır: işlevin kendi adımlarını sayar ve her çağrıda yeniden sıfırdan
başlar. Tablo `hazir` bayrağıyla ilk çağrıda kurulur; ikinci çağrıda bayrak doludur ve
kurulum atlanır.

#### Isınma Kuralını Sayaçla Uygulamak

Dört kuraldan ilk üçü bir ölçüm programında şöyle görünür. Aynı girdi: her koşuda
`sorgula` aynı `bas=0` değeriyle çağrılır. Yineleme: beş koşu yapılır. Isınma: ilk koşu
ayrı raporlanır ya da ortalamadan çıkarılır. Her koşuda tablo aynı nesnede kalır,
yani ilk çağrı kurulumu yapar, sonrakiler yapmaz.

```cpp
// olcum.hpp — yineleme, isinma ve ortalama adim
#pragma once
#include <vector>

#include "tablo.hpp"

struct IsinmaSonucu {
    long isinmasiz = 0;
    long isinmali = 0;
    int yineleme = 0;
};

inline IsinmaSonucu isinma_olc(int yineleme) {
    Tablo tablo;
    std::vector<long> adimlar;
    for (int i = 0; i < yineleme; ++i) adimlar.push_back(sorgula(tablo, 0));
    long hepsi = 0;
    long ilk_haric = 0;
    for (int i = 0; i < yineleme; ++i) {
        hepsi += adimlar[i];
        if (i > 0) ilk_haric += adimlar[i];
    }
    IsinmaSonucu sonuc;
    sonuc.yineleme = yineleme;
    sonuc.isinmasiz = hepsi / yineleme;
    sonuc.isinmali = ilk_haric / (yineleme - 1);
    return sonuc;
}
```

Isınmasız ortalama beş koşunun hepsini içerir: `(110 + 10 + 10 + 10 + 10) / 5 = 30`.
Isınmalı ortalama ilk koşuyu atar: `(10 + 10 + 10 + 10) / 4 = 10`. İki sayı doğrudur, ama farklı
şeyi ölçer: ilki "bu işlevi sıfırdan beş kez çağıran bir program ne öder", ikincisi
"hazır bir tabloya bir sorgunun maliyeti nedir". Hangisinin hangi soruya cevap
verdiğini yazmadan bir sayı vermek yanıltıcıdır.

#### Ölçüm: Sıcak Nokta Hangisi

Aşağıdaki program üç sayacı okur, en yüksek olanı sıcak nokta olarak seçer ve tabloyu
iki kez sorgular.

```cpp
// profil_olcumu.cpp — aracli profil ve tembel kurulan tablo
#include <iostream>
#include <vector>

#include "eniyi.hpp"
#include "olcum.hpp"
#include "ornek.hpp"
#include "profil.hpp"
#include "tablo.hpp"

int main() {
    Profil p;
    std::vector<int> v;
    for (int i = 0; i < 100; ++i) v.push_back(deger_hazirla(p, i));
    int bulunan = ikizleri_say(v, p);
    std::cout << "aracli profil kosum hazirla=" << p.hazirla << " ara=" << p.ara
              << " ikiz=" << p.ikiz << "\n";

    const char* sicak = "hazirla";
    long en_cok = p.hazirla;
    if (p.ara > en_cok) { sicak = "ara"; en_cok = p.ara; }
    if (p.ikiz > en_cok) { sicak = "ikiz"; en_cok = p.ikiz; }
    std::cout << "sicak nokta " << sicak << " bulunan=" << bulunan << "\n";

    long artis = p.hazirla + p.ara + p.ikiz;
    std::cout << "enstrumantasyon kosum artis=" << artis << "\n";

    std::vector<int> iz = iz_kur();
    OrnekSayim sifir = ornekle(iz, 70, 0);
    OrnekSayim otuzbes = ornekle(iz, 70, 35);
    std::cout << "ornekleme kosum kaydirma=0 hazirla=" << sifir.adet[ISLEV_HAZIRLA]
              << " ara=" << sifir.adet[ISLEV_ARA] << "\n";
    std::cout << "ornekleme kosum kaydirma=35 hazirla=" << otuzbes.adet[ISLEV_HAZIRLA]
              << " ara=" << otuzbes.adet[ISLEV_ARA] << "\n";

    Profil p2;
    int bulunan_tablo = ikizleri_say_tablo(v, p2);
    std::cout << "eniyileme kosum once=" << p.ara << " sonra=" << p2.ara
              << " bulunan=" << bulunan << "/" << bulunan_tablo << "\n";

    Tablo tablo;
    long ilk = sorgula(tablo, 0);
    long ikinci = sorgula(tablo, 10);
    std::cout << "tembel tablo kosum ilk=" << ilk << " ikinci=" << ikinci << "\n";

    IsinmaSonucu isinma = isinma_olc(5);
    std::cout << "isinma kosum isinmasiz=" << isinma.isinmasiz
              << " isinmali=" << isinma.isinmali << " yineleme=" << isinma.yineleme << "\n";
    return 0;
}
```

```text
aracli profil kosum hazirla=100 ara=4950 ikiz=50
sicak nokta ara bulunan=50
enstrumantasyon kosum artis=5100
ornekleme kosum kaydirma=0 hazirla=2 ara=71
ornekleme kosum kaydirma=35 hazirla=1 ara=71
eniyileme kosum once=4950 sonra=150 bulunan=50/50
tembel tablo kosum ilk=110 ikinci=10
isinma kosum isinmasiz=30 isinmali=10 yineleme=5
```

**EB18.** Birinci satır üç sayacı verir: yüz değer hazırlandı, `4950` karşılaştırma
yapıldı, `50` eşit çift bulundu. Sıcak nokta sayaçtan okunur: `ara` en büyük sayaçtır ve
`hazirla`'nın kırk dokuz katından fazladır. Eniyilenecek yer `ikizleri_say` içindeki iç içe döngüdür;
`deger_hazirla` ya da eşit çiftleri sayma değil. Bu seçimi yapan tahmin değil, sayaçtır.

**EB19.** İkinci satır `bulunan=50` ile eşit çiftlerin sayısını basar. Sonucun basılması
ölü kod silmeye karşı da bir güvencedir: hesap kullanıldığı için derleyicinin onu silmesi
gözlenebilir davranışı değiştirirdi.

**EB20.** Sıfır kaydırmayla örnekleme `hazirla=2 ara=71`, otuz beş kaydırmayla
`hazirla=1 ara=71` verdi. Gerçekte yüz adım, 5050 adımın yaklaşık yüzde ikisidir ve
iki koşu da `hazirla`'yı bu payın etrafında, ama farklı sayıda gördü. İki koşuda da
sıcak nokta `ara`. Örneklemenin söylediği "hangisi sıcak" sorusunun cevabıdır; küçük
işlevlerin payı için kaydırmaya bağlı bir yuvarlama hatası taşır.

**EB21.** Altıncı satır eniyilemenin işlem sayısını gösterir: `once=4950`, `sonra=150`.
İkisi de `bulunan=50` verdi; sonuç değişmedi. Önce ve sonra aynı girdiye ve aynı
sayaca bağlı olduğu için karşılaştırma geçerlidir. Sayı süre değildir: iki yazım farklı
cins adım atar ve süreyle ilgili hüküm vermek için bu sayı yetmez.

**EB22.** Isınmasız ortalama `30`, ısınmalı ortalama `10`; ikisi de aynı girdiyle ve beş
yinelemeyle hesaplandı. Farkın kaynağı tek bir koşudur: kurulumu yapan ilk koşu. Beş
yineleme bu bir koşunun payını ortalamada seyreltti ama silmedi. Yineleme ısınmanın yerini
tutmaz; kurulumu yapan koşu ayrıca adlandırılmalıdır.

**EB23.** Enstrümantasyonun kendisi de iş yaptı: `hazirla` sayacı yüz, `ara` sayacı
4950, `ikiz` sayacı elli kez artırıldı; toplam `artis=5100`. Ölçülen program, ölçülmemiş
programdan 5100 artış kadar farklıdır. Örnekleme kodu değiştirmediği için bu farkın
çoğundan kaçınır; bedeli ise kesin sayımdan vazgeçmektir.

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

Bu dersin ölçtüğü düzenek, sayaçla sıcak noktayı bulmaktır; `tembel tablo` satırı onun sınırını
gösterir. Aynı `sorgula` işlevi ilk çağrıda `ilk=110` adım, ikincide `ikinci=10` adım
saydı: kurulum ilk ölçüme girdi, ikinciye girmedi. İki sorguyu karşılaştırıp "ikinci
sorgu on bir kat az iş yapıyor" demek yanlış olurdu; kurulum bir kez ödeniyor ve sonra
hazır bekliyor.

Bu, ölçümün **başladığı yere** bağlı olduğunu gösterir. Ölçüm ilk çağrıdan başlıyorsa
tablonun kurulumu dahildir; ikinci çağrıdan başlıyorsa dahil değildir. İki sonuç da
doğrudur, ama farklı sorulara cevap verir. Bir profilin sınırı şudur: sayaç ne
sayıyorsa onu söyler; hangi koşuyu ölçüme aldığınız ise sonucu sizin seçimleriniz
belirler. Bu yüzden her ölçümün yanında başlangıç noktası da yazılır.

#### Özet

- Bu dersin ölçtüğü düzenek bedeli **koşum** sınıfına taşır: profilleme koşarken ödenir
  ve sonucu koşan program verir; ölçü süre değil, sayaçtır.
- Enstrümantasyon koda sayaç yerleştirir ve kesin sayar ama koda dokunur; örnekleme kodu
  değiştirmez, yükü daha küçüktür ama sonucu yaklaşıktır.
- Sıcak nokta tahminden değil, sayaçtan bulunur: `aracli profil` üç sayaçtan `ara`
  sayacını `4950` ile en yüksek buldu.
- Karşılaştırmalı ölçüm ısınma, yineleme, aynı girdi ve ölü kod silme kurallarını ister;
  sonucu kullanmak son kurala karşı güvencedir.
- `tembel tablo` aynı işlevi ilk çağrıda `110`, ikincide `10` adımda saydı: ölçümün nerede
  başladığı sonucu değiştirir, bu yüzden başlangıç noktası ölçümle birlikte yazılır.

#### Sonraki Adım

Profil sıcak noktayı bulduktan sonra bir başka bedel kalır: kod ne kadar sık
yeniden derleniyor. Bir sonraki ders derleme süresini yönetmeyi açıyor: gizleme deyimi
derlemeden kazandığını nereye ödüyor sorusuyla başlık bağımlılığını azaltacağız.

### Derleme Süresi Yönetimi

Source: https://academia.sh/tr/kurslar/cpp-sablonlar/derleme-suresi-yonetimi

İleri bildirim, gereksiz include azaltma, gizleme deyimi (pimpl), opak tip, açık somutlaştırma ve extern template ile derlemeden kazanılan bedelin nesne başına koşuma nasıl taşındığı.

Önceki ders, profilin sıcak noktayı bulduğunu ve ölçümün nerede başladığının sonucu
değiştirdiğini gösterdi. Ama hiçbir sayaç yakalayamayan bir bedel daha var: programı
koşmadan önce ödenen derleme bedeli. Bir başlık dosyası değiştiğinde, o başlığı doğrudan
ya da dolaylı olarak içeren her çeviri birimi yeniden derlenir. Başlık ne kadar çok
birime görünüyorsa değişiklik o kadar pahalıdır. Bu ders o görünürlüğü azaltmanın
yollarını inceliyor ve bir yolun, **gizleme deyiminin (pimpl)**, kazandığı derleme
bedelini koşumda nereye ödettiğini ölçüyor. Cevabın sınıfı **koşum**dur. M11/K05'ten bir
ayrım: orada taşınmış nesnenin durumu ölçü değildi; burada da taşınmış nesnenin durumuna
bakmıyoruz, yalnızca taşımanın bir ayırma yapıp yapmadığını sayıyoruz. Derleme süresi
basılmaz; bağımlılık "bu başlığı kaç birim görüyor" diye anlatılır, ölçülen sayı ise
nesne başına ayırmadır.

#### Başlığın Görünürlüğünü Azaltmak

İlk araç **ileri bildirimdir**. Bir başlık başka bir tipi yalnızca işaretçi ya da
referans olarak kullanıyorsa, o tipin tam tanımına ihtiyaç duymaz; `class Pencere;` yazmak
yeter. Tam tanımı içeren başlığı `#include` etmek, o başlığın değiştiği her seferde bu
başlığı da yeniden derletir. İleri bildirim bu zinciri keser: tam tanım yalnızca onu
gerçekten kullanan `.cpp` dosyasında içerilir.

İkinci araç, gereksiz `#include` satırlarını silmektir. Bir başlığa eklenen satır, o
başlığı içeren bütün birimlerin ödediği bir bedeldir; kullanılmayan bir satır da aynı
bedeli ödetir. Üçüncü araç şablonlara özgüdür: sık kullanılan bir şablon özelleşmesi
her birimde yeniden somutlaştırılıyorsa, **açık somutlaştırma** ve `extern template`
bildirimi bunu tek birime toplar. Başlıkta `extern template class Kutu<int>;` yazılır,
tek bir `.cpp` dosyasında `template class Kutu<int>;` ile somutlaştırma yapılır; öteki
birimler satır içi olmayan üyeleri yeniden üretmek yerine o birimdeki tanıma bağlanır. Bu araç için
aşağıda küçük bir örnek var; etkisi koşum çıktısında görünmez, bu yüzden ölçülmez.

#### İleri Bildirim Bir Başlığı Neden Hafifletir

İleri bildirimin işini bir örnekle görelim. Bir çizim işlevi `Pencere` tipini parametre
olarak alıyor. İşlevin **bildirimi** için tipin tam tanımı gerekmez: tamamlanmamış bir tip
bir işlev bildiriminde parametre olabilir, ister referans, ister değer olarak. Tam tanım
yalnızca işlevin **tanımında** ve işlevin **çağrıldığı** yerde gerekir. Çizim başlığı bu
yüzden yalnızca `class Pencere;` yazar ve `pencere.hpp`'yi hiç içermez; `pencere.hpp`
yalnızca `cizim.cpp` içinde görünür.

`cizim.hpp`'yi içeren bir birim `Pencere`'nin içini görmez; `Pencere`'nin üyeleri değişse
de o birim yeniden derlenmez. Birim yalnızca bir `Pencere`
nesnesi yaratıyor ya da üyesini çağırıyorsa tam tanımı kendisi içerir.

```cpp
// cizim.hpp — Pencere yalniz ileri bildirimle gorunur
#pragma once

class Pencere;

int ciz(const Pencere& p);
int ciz_kopya(Pencere p);
```

```cpp
// cizim.cpp — tam tanim yalniz burada icerilir
#include "cizim.hpp"
#include "pencere.hpp"

int ciz(const Pencere& p) { return p.genislik(); }
int ciz_kopya(Pencere p) { return p.genislik(); }
```

İki işlev aynı genişliği döndürür, ama parametre geçiş biçimi farklıdır. `ciz` referans
alır ve yeni bir `Pencere` kurmaz. `ciz_kopya` değer alır: çağrı sırasında bir kopya
kurulur ve gizleme deyimli sınıfın kopyası bir öbek ayırması yapar. Başlık aynı ileri
bildirimle iki işlevi de gösterir; bedel parametre biçimine bağlıdır, başlığa değil.

#### Açık Somutlaştırma ve extern template

Şablonlar için ileri bildirim yetmez: şablonun gövdesi başlıkta durmak zorundadır, çünkü
derleyici her kullanım yerinde tipi yerine koyar. Bir şablon çok sayıda birimde aynı
tiple kullanılırsa, her birim aynı somutlaştırmayı kendi içinde üretir ve bağlayıcı
kopyalardan birini seçer. Çalışan program doğrudur, ama bu üretim her birimin derleme
süresine yazılır.

`extern template` bu tekrarı keser. Başlıkta `extern template class Kutu<int>;` yazılırsa,
birimler `Kutu<int>`'in satır içi olmayan üyelerini kendi içlerinde somutlaştırmaz; tanımı
başka bir birimde bekler. Sınıf gövdesinde tanımlanan üyeler örtük olarak satır içidir ve
bildirim onları kapsamaz: derleyici bu üyeleri satır içine almak için yine üretebilir.
Kazanç, gövdesi dışarıda tanımlanan üyelerde büyür; aşağıdaki `Kutu`'nun üyeleri sınıf
gövdesinde tanımlı olduğu için örnek kazancı değil, yazımı gösterir. Tek bir `.cpp` dosyasında `template class Kutu<int>;` ile **açık somutlaştırma**
yapılır ve tanımın tek kopyası orada üretilir. Etki derleme süresinde görünür; çıktıda
görünmez. Aşağıdaki program yalnızca çift bildirimin derlendiğini ve bağlandığını
doğrular.

```cpp
// kutu.hpp — sablon govdesi basliktadir, int ozellesmesi disaridan gelir
#pragma once

template <typename T>
class Kutu {
public:
    explicit Kutu(T deger) : deger_(deger) {}
    T al() const { return deger_; }

private:
    T deger_;
};

extern template class Kutu<int>;
```

```cpp
// kutu.cpp — Kutu<int> somutlastirmasinin tek tanimi
#include "kutu.hpp"

template class Kutu<int>;
```

#### Gizleme Deyimi

Dördüncü araç sınıfın **özel üyelerini** de başlıktan çıkarır. Bir sınıfın özel alanları
başlıkta görünür, çünkü derleyici nesnenin boyutunu bilmek zorundadır. Bir özel alanın
tipi değişirse, sınıfı içeren her birim yeniden derlenir. **Gizleme deyimi** bu alanları
`Ic` adlı iç bir yapıya taşır, başlıkta yalnızca onun ileri bildirimini ve bir
`std::unique_ptr<Ic>` tutar. `Ic` bu noktada **opak tip**tir: başlığı gören birim onun
var olduğunu bilir, içini bilmez.

Aşağıdaki başlık yalnızca ileri bildirimi ve işaretçiyi taşır. Yıkıcının ve taşıma
işlemlerinin burada yalnızca **bildirildiğine** dikkat edin; tanımları `.cpp` dosyasındadır.

```cpp
// pencere.hpp — yalniz ileri bildirim ve unique_ptr
#pragma once
#include <memory>

class Pencere {
public:
    Pencere();
    ~Pencere();
    Pencere(Pencere&&) noexcept;
    Pencere& operator=(Pencere&&) noexcept;
    Pencere(const Pencere&);
    Pencere& operator=(const Pencere&);

    int genislik() const;
    void genislik_ata(int yeni);

    static int nesne_sayisi();
    static int ayirma_sayisi();

private:
    struct Ic;
    std::unique_ptr<Ic> ic_;
};
```

`std::unique_ptr<Ic>` yıkılırken `Ic`'nin yıkıcısını çağırır; bunun için `Ic`'nin tam
tanımını görmek zorundadır. Başlıkta `Ic` yalnızca ileri bildirimle durduğundan, yıkıcıyı
başlıkta örtük bırakmak ya da `= default` yazmak derleyiciyi `Ic` tamamlanmamış tipken
yıkıcıyı üretmeye çalıştırır ve derleme hata verir. Bu yüzden yıkıcı ve taşıma işlemleri
yalnızca bildirilir, `Ic`'nin tanımlandığı `.cpp` dosyasında `= default` ile tanımlanır.

```cpp
// pencere.cpp — Pencere gerceklemesi, Ic burada tanimli
#include "pencere.hpp"

namespace {
int nesne = 0;
int ayirma = 0;
}  // namespace

struct Pencere::Ic {
    int genislik = 0;
};

Pencere::Pencere() : ic_(std::make_unique<Ic>()) {
    ++nesne;
    ++ayirma;
}

Pencere::~Pencere() = default;
Pencere::Pencere(Pencere&&) noexcept = default;
Pencere& Pencere::operator=(Pencere&&) noexcept = default;

Pencere::Pencere(const Pencere& diger) : ic_(std::make_unique<Ic>(*diger.ic_)) {
    ++ayirma;
}

Pencere& Pencere::operator=(const Pencere& diger) {
    if (this != &diger) *ic_ = *diger.ic_;
    return *this;
}

int Pencere::genislik() const { return ic_->genislik; }
void Pencere::genislik_ata(int yeni) { ic_->genislik = yeni; }
int Pencere::nesne_sayisi() { return nesne; }
int Pencere::ayirma_sayisi() { return ayirma; }
```

`Ic`'ye bir alan eklenirse ya da `genislik`'in tipi değişirse yalnızca `pencere.cpp`
yeniden derlenir. `pencere.hpp` olduğu gibi kalır, onu içeren hiçbir birim yeniden
derlenmez. Başlık değişmeden gerçeklemenin değişebilmesi, gizleme deyiminin kazandığı
derleme bedelidir.

#### Yıkıcı Neden .cpp Dosyasındadır

Bu düzenin en sık yapılan hatası yıkıcıyı başlıkta bırakmaktır. Kural basittir: `std::unique_ptr<T>`
yıkılırken `delete` çağrılır ve `delete` için `T`'nin tam tanımı gerekir. Başlıkta
`Pencere`'nin yıkıcısı yazılmamış ya da `= default` olarak yazılmışsa derleyici onu
**`Pencere` nesnesini yıkan her birimde** üretmeye çalışır. O birimlerde `Ic` hâlâ
tamamlanmamış bir tiptir ve `delete` geçersiz olur. Çözüm, yıkıcıyı başlıkta yalnızca
bildirmek, `Ic`'nin tam göründüğü `.cpp` dosyasında `= default` ile tanımlamaktır. Taşıyan
atamada da aynı gerekçe vardır: atama eski `unique_ptr`'yi yıkmak zorundadır. Taşıyan
yapıcı da `.cpp` dosyasında tanımlanır, çünkü kurulum yarıda kalırsa kurulmuş üyeleri
yıkan kod onda da üretilir.

Hata mesajını burada alıntılamıyoruz ve koşturmuyoruz; derleyiciden derleyiciye
değişir. Bilinmesi gereken, hatanın **kullanıcı birimde** çıktığıdır: yıkıcının
yazıldığı yerde değil, onu çağıran yerde. Bu, hatayı bulmayı zorlaştırır.

`std::shared_ptr` ile karşılaştırınca fark görülür. `shared_ptr` silicisini nesne
**kurulurken** yakalar; tamamlanmamış bir tip için de yıkıcı başlıkta yazılabilir, çünkü
silici kurulumun yapıldığı birimde, tip tam görünürken oluşur. `unique_ptr`'de silici tipin
parçasıdır ve yıkıldığı yerde üretilir. `shared_ptr`, bu sayede başlıktaki yıkıcı
kuralından kurtulur, ama karşılığında bir kontrol bloğu taşır: `std::make_shared` onu nesneyle tek
ayırmada kurar, ayrı kurulan bir `shared_ptr` ise onun için ikinci bir ayırma öder. Varsayılan
silicili `unique_ptr` ise yaygın gerçeklemelerde tek bir işaretçi boyundadır. Bu derste `unique_ptr` seçildi ve
ayırma sayısı onun üzerinden okunur.

#### Bu Başlığı Kaç Birim Görüyor

Derleme maliyeti, bir başlığın kaç çeviri birimine göründüğüyle ölçülür. Bunu çıktıdan
okuyamayız, projenin kurulumundan okuruz; bu yüzden sayıyı bir **sayım örneği** olarak
veriyoruz, ölçüm olarak değil. Beş çeviri biriminin bulunduğu varsayımsal bir proje
düşünelim; birimlerin hepsi `pencere.hpp`'yi içeriyor. `Ic` içindeki bir alanın tipi
değişince hangi birimler yeniden derlenir?

| Düzen | `pencere.hpp`'yi içeren birim | `Ic` değişince yeniden derlenen birim |
|---|---|---|
| Özel alanlar başlıkta | 5 | 5 |
| Gizleme deyimi, `Ic` `.cpp`'de | 5 | 1 |

Tablodaki sayılar bu varsayımsal projenin kurulumundan çıkar. İkinci satırda yeniden
derlenen tek birim `Ic`'yi tanımlayan `pencere.cpp`'dir. Aynı mantıkla `cizim.hpp`'nin
`pencere.hpp`'yi içermesi, `cizim.hpp`'yi içeren her birimi `pencere.hpp`'ye de bağlardı;
ileri bildirim bu dolaylı bağlantıyı keser. Dolaylı içermeler bu sayıyı sessizce
büyütür: bir başlık, kendisini içeren başlıkların içerdiği her şeyi de getirir.

Bu yüzden bir başlığa `#include` eklemeden önce iki şey kontrol edilir. Eklenen tip
başlıkta gerçekten tam tanımla mı gerekiyor, yoksa ileri bildirim yeterli mi? Ve bu
başlığı kaç başlık, onlar da kaç birim içeriyor? Zincirin uzunluğu bu soruların
yanıtından çıkar; derleyici bu sayımı bizim yerimize yapıp uyarmaz, çünkü program
doğru derlenir ve yalnızca daha çok iş yapılır.

#### Ölçüm: Nesne Başına Ayırma

```cpp
// pencere_olcumu.cpp — gizleme deyiminin nesne basina ayirmasi
#include <iostream>
#include <utility>

#include "cizim.hpp"
#include "kutu.hpp"
#include "pencere.hpp"

int main() {
    Pencere a;
    Pencere b;
    Pencere c;
    a.genislik_ata(640);
    b.genislik_ata(480);
    std::cout << "gizleme deyimi kosum nesne=" << Pencere::nesne_sayisi()
              << " ayirma=" << Pencere::ayirma_sayisi() << "\n";

    int once = Pencere::ayirma_sayisi();
    Pencere tasinan(std::move(a));
    int tasima = Pencere::ayirma_sayisi() - once;

    once = Pencere::ayirma_sayisi();
    Pencere kopya(b);
    int kopyalama = Pencere::ayirma_sayisi() - once;

    std::cout << "gizleme deyimli nesne kosum tasima=" << tasima
              << " kopya=" << kopyalama << "\n";
    std::cout << "genislik tasinan=" << tasinan.genislik()
              << " kopya=" << kopya.genislik() << "\n";

    int baslangic = Pencere::ayirma_sayisi();
    int g1 = ciz(b);
    int referans = Pencere::ayirma_sayisi() - baslangic;
    baslangic = Pencere::ayirma_sayisi();
    int g2 = ciz_kopya(b);
    int deger = Pencere::ayirma_sayisi() - baslangic;
    std::cout << "ileri bildirim kosum referans=" << referans << " deger=" << deger
              << " genislik=" << g1 + g2 << "\n";

    Kutu<int> kutu(7);
    std::cout << "acik somutlastirma kosum kutu=" << kutu.al() << "\n";
    return 0;
}
```

```text
gizleme deyimi kosum nesne=3 ayirma=3
gizleme deyimli nesne kosum tasima=0 kopya=1
genislik tasinan=640 kopya=480
ileri bildirim kosum referans=0 deger=1 genislik=960
acik somutlastirma kosum kutu=7
```

**EB24.** İlk satır, üç `Pencere` nesnesinin her birinin kuruluşta bir `Ic` ayırdığını
gösterir: `nesne=3`, `ayirma=3`. Gizleme deyimi, başlığın görünürlüğünü azaltmanın
bedelini her nesnede bir öbek ayırması olarak ödettirir; iç yapı artık nesnenin içinde
değil, ayrı bir bellek bloğundadır.

**EB25.** Üçüncü satır iki nesnenin doğru değerleri taşıdığını doğrular: taşınan nesne
`640`, kopya `480` genişliğini aldı. Taşınmış `a` nesnesinin durumuna bakılmadı; bu ders
onun durumunu değil, taşımanın ayırma yapıp yapmadığını ölçüyor.

**EB26.** Dördüncü satır, ileri bildirimle gösterilen `Pencere`'nin referansla geçişte
hiç ayırma yapmadığını (`referans=0`), değerle geçişte bir ayırma yaptığını (`deger=1`)
gösterir. Genişlik toplamı `960`, iki çağrının da `480` değerini okuduğunu doğrular.
Başlık iki işlevi de aynı ileri bildirimle taşıdı; bedel başlıktan değil, çağrı
biçiminden geldi.

**EB27.** Beşinci satır `kutu=7` basar. `extern template` bildirimi ve `template class`
tanımı birlikte derlendi ve bağlandı; çıktı tek bir somutlaştırma olduğunu göstermez, yalnızca
çift bildirimin geçerli olduğunu gösterir. Derleme süresindeki etki bu çıktıdan okunmaz.

**EB28.** Tablodaki beşe karşı bire hiçbir çıktı doğrulamıyor; bu, projenin
kurulumundan çıkan bir sayım örneğidir. Doğruluğu varsayıma bağlıdır: beş birimin
hepsinin `pencere.hpp`'yi içerdiği ve yalnızca birinin `Ic`'yi tanımladığı. Gerçek bir
projede dolaylı içermeler bu sayıyı değiştirir.

**EB29.** İkinci satırdaki `tasima=0`, taşıyan yapıcının `.cpp` dosyasında `= default`
ile tanımlanmasına bağlıdır: `Ic` tam tanımlıyken taşıma yalnızca `unique_ptr`'yi
devralır. Aynı yapıcı başlıkta `= default` ile tanımlansaydı `Ic` tamamlanmamış olduğu
için sorun çıkabilirdi. Bu yüzden ölçüm yalnızca kuralı uygulayan bir düzende anlamlıdır.

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

Bu dersin ölçtüğü düzenek, derlemeden kazanılan bedeli nesne başına koşuma taşımaktır.
İkinci satır bu bedelin ne zaman yeniden ödendiğini gösterir. Gizleme deyimli bir nesneyi
**taşımak** yeni ayırma yapmaz: `tasima=0`; çünkü taşıma yalnızca `unique_ptr`'nin
işaretçisini devralır, `Ic` yerinde kalır. Aynı nesneyi **kopyalamak** ise `Ic`'yi çoğaltır
ve bir ayırma yapar: `kopya=1`.

Yani kazanılan derleme bedeli, nesne üretildiği her yerde koşum bedeline dönüşür:
üretme ve kopyalama bir ayırma öder, taşıma ödemez. İleri bildirim aynı sınırı gösterir: `referans=0` ve `deger=1`. Referansla geçen çağrı
gizleme deyiminin bedelini ödemez; değerle geçen ödeyen çağrıdır. Sınır şudur: sınıf sıcak bir
döngüde yüz bin kez kopyalanıyorsa, derlemede kazanılan süre koşumda yüz bin ayırmaya
karşılık gelir. Bu yüzden gizleme deyimi, nesnelerin seyrek üretildiği, başlığın çok
birime görünür olduğu ve gerçeklemenin sık değiştiği sınıflar için gerekçelidir.

Karar verirken üç soru sorulur. Sınıf kaç birime görünür? Gerçekleme ne sıklıkla değişir?
Nesne kaç kez üretilir ya da kopyalanır? İlk iki sorunun yanıtı büyükse ve üçüncüsü
küçükse gizleme deyimi kazandırır. Üçüncü soru büyükse bedel koşumda geri ödenir ve
ayırma sayısı, bu derste olduğu gibi, doğrudan sayılarak kontrol edilmelidir. Sayım yapmadan
verilen karar bir tahmindir; dersin amacı bu tahmini bir ölçüme çevirmektir. Ölçüm de
yalnızca sayıyı verir: kazanılan derleme süresi bu çıktıdan okunamaz, çünkü derleme
süresi bu programın çıktısında yer almaz.

#### Özet

- Bu dersin ölçtüğü düzenek bedeli **koşum** sınıfına taşır: gizleme deyimi başlığın
  görünürlüğünü azaltır ve bunun karşılığında her nesne bir öbek ayırması ödemek
  zorunda kalır.
- Başlık değiştiğinde onu içeren her birim yeniden derlenir; ileri bildirim, gereksiz
  `#include` silme ve `extern template` bu zinciri kısaltır.
- Gizleme deyimi özel üyeleri `Ic` arkasına saklar; başlık değişmeden gerçekleme
  değişebilir. Yıkıcı ve taşıma işlemleri `Ic` tam tanımlıyken, `.cpp` dosyasında
  tanımlanır.
- `gizleme deyimi` üç nesnede `nesne=3 ayirma=3` ölçtü: her nesne bir ayırma öder.
- İleri bildirim başlığı hafifletir; `referans=0` ve `deger=1` ile bedel çağrı
  biçimine bağlıdır. `extern template` şablon somutlaştırmasını tek birime toplar.
- Taşıma `tasima=0` ayırma, kopyalama `kopya=1` ayırma yaptı: kazanılan derleme bedeli
  kopyalamada koşumda yeniden ödenir.

#### Sonraki Adım

Bu ders bir başlığın görünürlüğünü azaltmayı gösterdi; ama azaltmanın etkisini hâlâ
tahminle anlattık. Sıradaki ders kursun kapanışıdır: değişen bir başlık kaç birimi
yeniden derletiyor sorusuyla bu kursta ölçtüğümüz bütün düzenekleri birlikte
değerlendireceğiz.

### Paket Yönetimi ve Derleme Araçları

Source: https://academia.sh/tr/kurslar/cpp-sablonlar/paket-yonetimi-ve-derleme-araclari

Bağımlılık tablosu, sürüm sabitleme ve kilit dosyasıyla yeniden üretilebilir kurulum; yalnız başlıktan oluşan şablon kütüphanesinin her birimi yeniden derletmesi.

Önceki ders, bir başlığın görünürlüğünü azaltmanın bedelini nesne başına ayırmaya
taşıdığını gösterdi, ama kazanılan derleme süresini tahminle anlattı. Bu ders o tahmini
bir sayıya çeviriyor: değişen bir başlık kaç birimi yeniden derletiyor? Aynı soru bir
projenin dışarıdan getirdiği kütüphaneler için de sorulur; çünkü bir kütüphane de
sonunda birkaç başlıktır. Süre basılmaz. Ölçülen sayı, bir değişikliğin yeniden derlettiği
**çeviri birimi** sayısıdır. M10/K03'ten bir ayrım: sayı ölçünün kendisi değil, kanıt
aracıdır. Burada da sayı sınıflandırmanın kanıtıdır; ölçü sorusunun yanıtı bir sınıf
adıdır ve bu dersin yanıtı **derleme**dir.

#### Bağımlılık Tablosu

Bir **derleme sistemi** işini tek bir tabloya dayandırır: her birim hangi başlıkları
içeriyor? Bir dosya değişince, o dosyayı içeren birimler eskimiş sayılır; ötekilere
dokunulmaz. Sistemin adı ve yapılandırma dili bu dersin konusu değil, tablonun kendisi
konusu. Tabloyu sade C++ ile kurabiliriz: birim adı ve içerdiği başlıkların listesi.

```cpp
// bagimlilik.hpp — birim ve icerdigi basliklar tablosu
#pragma once
#include <string>
#include <vector>

struct Birim {
    std::string ad;
    std::vector<std::string> basliklar;
};

inline int yeniden(const std::vector<Birim>& birimler, const std::string& degisen) {
    int sayi = 0;
    for (const auto& b : birimler) {
        if (b.ad == degisen) {
            ++sayi;
            continue;
        }
        for (const auto& h : b.basliklar) {
            if (h == degisen) {
                ++sayi;
                break;
            }
        }
    }
    return sayi;
}
```

`yeniden` her birime bakar. Birimin kendi kaynağı değiştiyse onu sayar; değilse başlık
listesinde değişen dosyayı arar ve ilk eşleşmede durur. Aynı başlık bir birimde iki kez
geçse bile birim bir kez sayılır, çünkü bir birim bir kez derlenir. Dolaylı içermeler de
tabloya yazıldığı sürece bu sayıma girer; bir başlığın getirdiği başlık, listeye yazılmış
başlıktan farksızdır.

#### Sürüm Sabitleme ve Kilit Dosyası

Dışarıdan gelen bir kütüphaneyi bir **paket yöneticisi** getirir. Projenin ona bağımlılığı
bir sürüm aralığıyla yazılır: "1.0.0 dahil, 2.0.0 hariç". Aralık bir söz verir, tek bir
sürümü göstermez. Kayıt yeni bir sürüm yayımladığında aynı aralık başka bir sürüme çözülür.
**Sürüm sabitleme** bu kaymayı keser: çözülen sürüm bir **kilit dosyası**na yazılır ve
sonraki kurulumlar aralığı yeniden çözmek yerine kilitteki sürümü kullanır.

Bir ayrım gerekiyor. Kilit dosyası, bu kursun eşzamanlılık bölümündeki dışlama kilidiyle
yalnızca adı paylaşır.
Dışlama kilidi çalışan iş parçacıklarını sıraya sokar; kilit dosyası ise diskte duran
bir kayıttır ve hiçbir şeyi engellemez, yalnızca hangi sürümün kurulduğunu hatırlar.

```cpp
// surum.hpp — surum araligi ve kilit dosyasi modeli
#pragma once
#include <map>
#include <string>
#include <vector>

// Surum 130, 1.3.0 olarak kodlanir; kayit yayimlanmis surumlerin listesidir.
inline int coz(const std::vector<int>& kayit, int alt, int ust) {
    int secilen = -1;
    for (int s : kayit) {
        if (s >= alt && s < ust && s > secilen) secilen = s;
    }
    return secilen;
}

using KilitDosyasi = std::map<std::string, int>;

inline int kur(const KilitDosyasi& kilit, const std::string& paket,
               const std::vector<int>& kayit, int alt, int ust) {
    auto it = kilit.find(paket);
    if (it != kilit.end()) return it->second;
    return coz(kayit, alt, ust);
}
```

`coz` aralıktaki en yüksek sürümü seçer. `kur` önce kilide bakar; paket kilitteyse
aralığı hiç çözmez. Kilit boşsa kurulum aralığa güvenir ve sonucu kaydın o anki haline
bağlar. **Yeniden üretilebilir kurulum** bu farka dayanır: kilitteki sürüm kayıtta durduğu
sürece, aynı kilit dosyası kayıt değişse de aynı sürümü verir.

#### Yalnız Başlıktan Oluşan Kütüphane

Bir şablon kütüphanesi çoğu zaman **yalnız başlıktan oluşan kütüphane**dir (header-only
library). Sebebi kursun ilk dersinde görüldü: derleyici şablonu her kullanım yerinde tipi
yerine
koyarak üretir, bu yüzden gövde kullanan her birimde görünür olmak zorundadır. Her tip
için ayrı gövde üretilir ve bu üretim, o başlığı içeren her birimin derleme süresine
yazılır. Gövde başlıkta durduğu için kütüphanenin içindeki bir değişiklik, başlığı
içeren birimlerin hepsini eskitir.

Derlenmiş bir kütüphane başka bir düzen kurar. Başlık yalnızca arayüzü bildirir; gövde
tek bir `.cpp` dosyasında durur. Gövdedeki değişiklik tek birimi yeniden derletir.
Ancak bu düzen, önceki dersin açık somutlaştırmasındaki gibi yalnızca önceden seçilmiş
tipler için çalışır. Her tipe açık bir kütüphane, şablon olmaktan çıkar.

```cpp
// olcum.cpp — degisen baslik kac birimi yeniden derletiyor
#include <iostream>

#include "bagimlilik.hpp"
#include "surum.hpp"

int main() {
    std::vector<Birim> birimler = {
        {"giris.cpp", {"ortak.hpp"}},
        {"hesap.cpp", {"ortak.hpp"}},
        {"rapor.cpp", {"ortak.hpp", "ozel.hpp"}},
    };
    std::cout << "degisen baslik derleme ortak=" << yeniden(birimler, "ortak.hpp")
              << " ozel=" << yeniden(birimler, "ozel.hpp") << "\n";

    std::vector<int> kayit = {100, 110, 130};
    KilitDosyasi kilit;
    int ilk = kur(kilit, "sayac", kayit, 100, 200);
    kilit["sayac"] = ilk;
    kayit.push_back(140);
    kayit.push_back(210);
    int kilitsiz = kur(KilitDosyasi{}, "sayac", kayit, 100, 200);
    int kilitli = kur(kilit, "sayac", kayit, 100, 200);
    std::cout << "surum sabitleme ilk=" << ilk << " kilitsiz=" << kilitsiz
              << " kilitli=" << kilitli << "\n";

    std::vector<Birim> yalniz = {
        {"giris.cpp", {"ortak.hpp", "kutuphane.hpp"}},
        {"hesap.cpp", {"ortak.hpp", "kutuphane.hpp"}},
        {"rapor.cpp", {"ortak.hpp", "ozel.hpp", "kutuphane.hpp"}},
        {"cizim.cpp", {"kutuphane.hpp"}},
    };
    std::vector<Birim> derlenmis = {
        {"giris.cpp", {"ortak.hpp", "arayuz.hpp"}},
        {"hesap.cpp", {"ortak.hpp", "arayuz.hpp"}},
        {"rapor.cpp", {"ortak.hpp", "ozel.hpp", "arayuz.hpp"}},
        {"cizim.cpp", {"arayuz.hpp"}},
        {"kutuphane.cpp", {"arayuz.hpp"}},
    };
    std::cout << "yalniz baslik govde=" << yeniden(yalniz, "kutuphane.hpp")
              << " derlenmis govde=" << yeniden(derlenmis, "kutuphane.cpp")
              << " arayuz=" << yeniden(derlenmis, "arayuz.hpp") << "\n";
    return 0;
}
```

```text
degisen baslik derleme ortak=3 ozel=1
surum sabitleme ilk=130 kilitsiz=140 kilitli=130
yalniz baslik govde=4 derlenmis govde=1 arayuz=5
```

**EB30.** İlk satır bağımlılık tablosunun sonucudur: `ortak.hpp` değişince üç birimin üçü
de yeniden derlenir (`ortak=3`), `ozel.hpp` değişince yalnızca `rapor.cpp` (`ozel=1`).
Aynı kod tabanında iki başlığın bedeli üçe bir farkla ayrışır; fark başlığın büyüklüğünden
değil, kaç birimin onu içerdiğinden gelir.

**EB31.** İkinci satırda `ilk=130`: kayıt henüz `100`, `110` ve `130` sürümlerini taşırken
aralık `130`'a çözüldü ve sonuç kilide yazıldı. Kayda `140` ve aralığın dışında kalan
`210` eklendi. Kilitsiz kurulum `140`'ı seçti (`kilitsiz=140`), çünkü `210` aralıkta
değil; kilitli kurulum hâlâ `130` verdi (`kilitli=130`).

**EB32.** Yukarıdaki iki kurulum aynı projenin iki koşumudur ve arada tek fark kaydın
değişmesidir. Kilitli olan değişmedi, kilitsiz olan kaydı. Bu çıktı sürüm numarasının
kendisini değil, aralığın kaymasını gösterir; `130` ve `140` yalnızca modelin
kodlamasıdır.

**EB33.** Üçüncü satırda yalnız başlıktan oluşan kütüphanenin gövdesi değişince dört
birimin dördü yeniden derlenir (`govde=4`). Derlenmiş kütüphanede aynı gövde değişikliği
tek birimi, `kutuphane.cpp`'yi eskitir (`govde=1`).

**EB34.** `arayuz=5`: derlenmiş kütüphanenin yalnızca bildirimleri taşıyan başlığı
değişirse, onu içeren dört birimin yanında `kutuphane.cpp` de yeniden derlenir. Derlenmiş
düzen gövde değişikliğini tek birime indirir, arayüz değişikliğini indirmez.

**EB35.** Dört ve beş birimlik tablolar kurulumdan çıkar, ölçülen bir derleme değildir.
Sayı, tabloya yazdığımız içermelere bağlıdır; gerçek bir projede dolaylı içermeler eksik
yazılırsa sayı olduğundan küçük çıkar. Model bu yüzden bir sayım örneğidir, derleme süresi
hakkında bir şey söylemez.

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

Bu dersin ölçtüğü düzenek, değişen bir başlığın yeniden derlettiği birim sayısıdır. Sınır,
yalnız başlıktan oluşan şablon kütüphanesindedir. Kütüphane bir şablon taşıdığında gövdesi
başlıkta durmak zorundadır ve onu içeren **her** birim gövdeyi kendi içinde yeniden
üretir. Kütüphanenin gövdesinde yapılan en küçük değişiklik de bu yüzden dört birimin
dördünü eskitir: `govde=4`. Derlenmiş kütüphanede aynı değişiklik `govde=1` verir.

Yani şablonun bedeli, kursun başında görülen gövdenin her birimde görünmesi, derlemeye
geri döner. Şablon kullanıcıya her tip için çalışma özgürlüğü verir; karşılığında gövde
başlıkta kalır ve her değişiklik içeren birimlerin sayısıyla çarpılır. Her kütüphaneyi
derlenmiş hâle getirmek genel bir çözüm değildir, çünkü derlenmiş düzen şablonun tipten
bağımsızlığını yalnızca açık somutlaştırılan tiplerle sınırlar. Seçim, bir ödünleşimdir:
gövde sık değişiyor ve başlık çok birime görünüyorsa derlenmiş düzen kazandırır; gövde
nadiren değişiyorsa yalnız başlıktan oluşan düzenin sadeliği yeter.

Sayının yeri de önemlidir. `govde=4` bir ölçünün yanıtı değildir; yanıt, düzeneğin
**derleme** sınıfına girdiğidir. Sayı bu sınıflandırmanın kanıtıdır: bedel çalışan
programda değil, programı kurmadan önce ödeniyor. Bu dersin düzenekleri koşum, bellek ya
da bekleme sınıfında satır doğurmaz; hepsi derleme sınıfındadır.

#### Özet

- Bu dersin ölçtüğü düzenek bedeli **derleme** sınıfına taşır: değişen bir başlık, onu
  içeren her birimi yeniden derletir.
- Bağımlılık tablosu `ortak=3 ozel=1` verdi: aynı projede iki başlığın bedeli, kaç birim
  tarafından içerildiğine göre ayrışır.
- Sürüm aralığı bir söz verir, kilit dosyası bir sürüm hatırlar: `kilitsiz=140` iken
  `kilitli=130`, yani yeniden üretilebilir kurulum kilide bağlıdır. Kilit dosyası dışlama
  kilidinden ayrıdır.
- Yalnız başlıktan oluşan kütüphane gövde değişince `govde=4` birimi eskitir; derlenmiş
  kütüphane `govde=1` eskitir, ama arayüz değişince `arayuz=5`.
- Şablonun bedeli, gövdenin her birimde görünmesi, derlemeye geri döner; derlenmiş düzen
  bunu ancak önceden seçilmiş tiplerle azaltabilir.

#### Kurs Kapanışı

Bu kurs boyunca on iki düzeneği aynı soruyla ölçtük: bedel nereye ödeniyor? Aşağıdaki
tablo her dersin ölçtüğü düzeneği, bedelin gittiği yeri ve o dersin sınırlayıcı ölçümünü
özetliyor.

| Ders | Ölçülen düzenek | Bedel nereye | Sınırlayıcı ölçüm |
| --- | --- | --- | --- |
| Fonksiyon ve Sınıf Şablonları | Şablon somutlaştırma | **derleme** | `const int` argümanı yeni gövde doğurmaz |
| Şablon Özelleştirme | `bool` özelleştirmesi | **bellek** | Okuma başına iki adım: bedel koşuma döner |
| Değişken Sayıda Şablon Parametresi | Özyinelemeli paket açma | **derleme** | Yeni tip listesi yeni gövde doğurur |
| Tip Özellikleri | `if constexpr` ile seçim | **derleme** | `volatile` argümanla hesap koşuma geçer: `adim=10` |
| Aşırı Yükleme Çözümlemesi ve SFINAE | `void_t` ile algılama | **derleme** | Gövdedeki hata elenmez, derlemeyi durdurur |
| Statik Çok Biçimlilik | CRTP ile gönderim | **derleme** | Karışık koleksiyon `std::variant` ister |
| İş Parçacıkları ve Kilitler | Kilitli sayaç | **bekleme** | Yerel toplam kilit alımını 4'e indirir |
| Bellek Modeli | `release`/`acquire` yayını | **bekleme** | Her eleman atomik olursa eşgüdüm 1000 olur |
| Önbellek Dostu Veri Düzeni | Yalnız `x` alanı | **bellek** | Üç alan birlikte okununca tablo tersine döner: `aos=0/11` |
| Profilleme ve Karşılaştırmalı Ölçüm | Araçlı profil | **koşum** | Tembel tabloda ilk çağrı 110, ikinci 10 adım |
| Derleme Süresi Yönetimi | Gizleme deyimi | **koşum** | Taşıma 0, kopya 1 ayırma |
| Paket Yönetimi ve Derleme Araçları | Değişen başlık | **derleme** | Yalnız başlıktan oluşan kütüphane her birimi eskitir |

**6 derleme · 2 koşum · 2 bellek · 2 bekleme; derlemede ödenen 6, koşumda ödenen 6.** Bu
dağılım kursun başında kurulan çekirdeğin dağılımıyla birebir aynıdır: altı düzenek bedeli
derlemeye yazar, altısı programı çalıştırırken tahsil eder. Gövdenin her birimde
görünmesi, kursun ilk dersinde şablonun bedeliydi; bu derste aynı bedel değişen başlık
başına birim sayısı olarak geri geldi.

Bu ders M11'in son dersidir; M11 burada kapanır.

