---
title: 'Kaynak Yönetimi ve Standart Kütüphane'
source: 'https://academia.sh/tr/kurslar/cpp-standart-kutuphane'
language: tr
updated: '2026-09-26T03:02:46+00:00'
license: 'CC BY-SA 4.0'
lessons: 11
outcomes:
    - 'Akıllı işaretçilerle sahiplik semantiğini tipte ifade eder'
    - 'Taşıma semantiğinin kopya maliyetini nasıl kaldırdığını açıklar'
    - 'Kapsayıcı ve algoritmaları uygun karmaşıklıkla seçer'
    - 'Bir işlemin kaynağı aynen mi bıraktığını, değiştirdiğini mi, boşalttığını mı, yoksa belirsiz bir durumda mı bıraktığını ayırt eder ve ölçer'
    - 'Taşınmış nesneye ve geçersiz yineleyiciye yalnız yeni değer verilebileceği yerleri tanır'
---

# Kaynak Yönetimi ve Standart Kütüphane

C++'ta bir işlem bittiğinde kaynağı olan nesne yaşamaya devam eder: kurs her düzeneği tek bir soruyla ölçer — bu işlem kaynağa ne bırakıyor; kaynak aynen mi kaldı, değişti mi, boşaldı mı, yoksa standart onun durumunu hiç söylemiyor mu? Akıllı işaretçiler, taşıma semantiği, sıfır ve beş kuralı, kapsayıcılar, yineleyiciler, algoritmalar, lambdalar, aralıklar ve tip çıkarımı aynı çerçeveden geçirilir.

## Sahiplik ve Taşıma

Akıllı işaretçiler, taşıma semantiği ve sağ değer referansları, sıfır ve beş kuralı, kopyalanamaz ve taşınamaz tipler — her biri bir işlemin kaynağa ne bıraktığı sorusuyla birlikte.

### Akıllı İşaretçiler

Source: https://academia.sh/tr/kurslar/cpp-standart-kutuphane/akilli-isaretciler

Bir unique_ptr, shared_ptr ya da weak_ptr üzerinde çalışan bir işlem bittiğinde kaynakta ne kaldığını dört sınıfla adlandırma: aynen, değişti, boş, belirsiz.

Önceki kurs bir ifadenin hangi nesneyle çalıştığını ölçtü ve kopyalamanın tek yol olmadığını hatırlatarak kapandı: bir işlem, hedefine yeni bir kopya açmak yerine kaynağın elindekini devralabilir. Bu devir işlemine taşıma denir. Taşıma bittiğinde kaynak nesne yok olmaz — yıkıcısı yine çalışacaktır — ama artık elinde neyin kaldığı işlemin türüne göre değişir. Bu kurs, bir işlemden sonra kaynakta ne kaldığını dört sınıfla adlandırıyor; bu ders o dört sınıfı akıllı işaretçiler (smart pointer) üzerinden tanıtıyor.

#### Dört Sınıf ve Çekirdeğin Tam Koşumu

Aşağıdaki üç dosya, bu kursun on bir dersinin ortak ölçüm aracını kurar. `kalan.hpp` dört sınıfı adlandırır:

```cpp
// kalan.hpp — islemden sonra kaynakta ne kaldigini adlandiran cekirdek
#ifndef KALAN_HPP
#define KALAN_HPP
#include <string>
enum class Kalan { aynen, degisti, bos, belirsiz };
// Once/sonra gozlemini sinifa cevirir. Bosalma esitlikten once denetlenir.
Kalan kiyasla(bool ayni_kaldi, bool bosaldi);
// Islem adini ve sinifi tek satirda basar, sinif sayacini artirir.
void raporla(const std::string& islem, Kalan k);
// Belirsiz kaynaga bakilmaz: once yeni deger verilir ya da kaynak yok edilir.
template <typename YeniDeger>
void belirsiz(const std::string& islem, YeniDeger yeni_deger) {
    yeni_deger();
    raporla(islem, Kalan::belirsiz);
}
// Sinif sayilarini ve ust ayrimi basar.
void ozet();
#endif
```

`kalan.cpp` bu dört sınıfı satır biçimine ve bir sayaca bağlar:

```cpp
// kalan.cpp — siniflama, satir bicimi ve sayac
#include "kalan.hpp"
#include <cstdio>
namespace {
int sayac[4] = {0, 0, 0, 0};
const char* const adlar[4] = {"aynen", "degisti", "bos", "belirsiz"};
}
Kalan kiyasla(bool ayni_kaldi, bool bosaldi) {
    if (bosaldi) return Kalan::bos;
    return ayni_kaldi ? Kalan::aynen : Kalan::degisti;
}
void raporla(const std::string& islem, Kalan k) {
    int i = static_cast<int>(k);
    ++sayac[i];
    std::printf("%-32s %s\n", islem.c_str(), adlar[i]);
}
void ozet() {
    std::printf("siniflar: aynen %d, degisti %d, bos %d, belirsiz %d\n",
                sayac[0], sayac[1], sayac[2], sayac[3]);
    std::printf("durumu soylenen %d, soylenmeyen %d\n",
                sayac[0] + sayac[1] + sayac[2], sayac[3]);
}
```

Kalan bölüm on bir işlemi ölçer ve bu dersin kendi akıllı işaretçi örneklerini ekler:

```cpp
// kaynagin-durumu.cpp — cekirdegin tam kosumu ve akilli isaretci derinlestirmesi
#include <algorithm>
#include <map>
#include <memory>
#include <ranges>
#include <string>
#include <utility>
#include <vector>
#include "kalan.hpp"
static void akilli() {
    auto p = std::make_unique<int>(7);
    int* once = p.get();
    auto q = std::move(p);
    raporla("unique_ptr tasindi", kiyasla(p.get() == once, p == nullptr));
}
static void tasima() {
    std::string s = "kucuk dize eniyilemesini asacak kadar uzun bir metin";
    std::string t = std::move(s);
    belirsiz("std::string tasindi", [&] { s = "yeni deger"; });
}
struct Kayit {
    std::string ad;
    ~Kayit() {}
};
static void bes_kurali() {
    Kayit a{"yikicisi yazilmis bir sinifin uzun adi"};
    std::string once = a.ad;
    Kayit b = std::move(a);
    raporla("yikicili sinifta std::move", kiyasla(a.ad == once, a.ad.empty()));
}
class Tutamac {
public:
    explicit Tutamac(int no) : no_(no) {}
    Tutamac(const Tutamac&) = delete;
    Tutamac& operator=(const Tutamac&) = delete;
    Tutamac(Tutamac&& diger) noexcept : no_(diger.no_) { diger.no_ = -1; }
    Tutamac& operator=(Tutamac&&) = delete;
    int no() const { return no_; }
private:
    int no_;
};
static void kopyalanamaz() {
    Tutamac a(3);
    int once = a.no();
    [[maybe_unused]] Tutamac b = std::move(a);
    raporla("kendi tasima yapicisi", kiyasla(a.no() == once, a.no() == -1));
}
static void kapsayici() {
    std::map<std::string, int> stok{{"elma", 4}};
    auto once = stok;
    int adet = stok["armut"];
    (void)adet;
    raporla("map'te operator[] ile okuma", kiyasla(stok == once, stok.empty()));
}
static void yineleyici() {
    std::vector<int> v{1, 2, 3, 4};
    auto it = v.begin() + 1;
    auto sonraki = v.erase(it);
    belirsiz("erase edilen yineleyici", [&] { it = sonraki; });
}
static void algoritma() {
    std::vector<int> v{5, 1, 4, 2};
    auto once = v;
    std::sort(v.begin(), v.end());
    raporla("std::sort", kiyasla(v == once, v.empty()));
}
static void sil_kaldir() {
    std::vector<int> v{1, 2, 3, 2, 5, 2};
    auto yeni_son = std::remove(v.begin(), v.end(), 2);
    belirsiz("std::remove sonrasi kuyruk", [&] { v.erase(yeni_son, v.end()); });
}
static void lambda() {
    int adim = 0;
    auto say = [adim]() mutable { return ++adim; };
    say();
    say();
    raporla("mutable degerle yakalama", kiyasla(adim == 0, false));
}
static void aralik() {
    std::vector<int> v{1, 2, 3};
    auto once = v;
    int toplam = 0;
    for (int n : v | std::views::transform([](int x) { return x * 10; })) toplam += n;
    (void)toplam;
    raporla("sol degerden view", kiyasla(v == once, v.empty()));
}
static void tip_cikarimi() {
    std::vector<int> v{1, 2, 3};
    auto once = v;
    for (auto& x : v) x *= 2;
    raporla("auto& ile dongu", kiyasla(v == once, v.empty()));
}
static void paylasimli() {
    auto p = std::make_shared<int>(9);
    auto once = p.get();
    auto kopya = p;
    raporla("shared_ptr kopyasi", kiyasla(p.get() == once, p == nullptr));
    std::printf("kullanim sayaci: %ld\n", static_cast<long>(p.use_count()));
    auto tasindi = std::move(p);
    raporla("shared_ptr tasindi", kiyasla(p.get() == once, p == nullptr));
}
static void zayif_referans() {
    auto p = std::make_shared<int>(21);
    std::weak_ptr<int> w = p;
    bool erisilebilir_once = !w.expired();
    p.reset();
    bool erisilebilir_sonra = !w.expired();
    std::printf("zayif referans erisimi: once %s, sonra %s\n",
                erisilebilir_once ? "evet" : "hayir",
                erisilebilir_sonra ? "evet" : "hayir");
}
static void yalniz_move() {
    auto p = std::make_unique<int>(5);
    int* once = p.get();
    auto&& r = std::move(p);
    (void)r;
    raporla("yalniz std::move sonrasi p", kiyasla(p.get() == once, p == nullptr));
}
int main() {
    akilli(); tasima(); bes_kurali(); kopyalanamaz(); kapsayici();
    yineleyici(); algoritma(); sil_kaldir(); lambda(); aralik(); tip_cikarimi();
    ozet();
    paylasimli();
    zayif_referans();
    yalniz_move();
}
```

Bu üç dosya birlikte derlenip çalıştırıldığında şu çıktı basılır:

```text
unique_ptr tasindi               bos
std::string tasindi              belirsiz
yikicili sinifta std::move       aynen
kendi tasima yapicisi            bos
map'te operator[] ile okuma      degisti
erase edilen yineleyici          belirsiz
std::sort                        degisti
std::remove sonrasi kuyruk       belirsiz
mutable degerle yakalama         aynen
sol degerden view                aynen
auto& ile dongu                  degisti
siniflar: aynen 3, degisti 3, bos 2, belirsiz 3
durumu soylenen 8, soylenmeyen 3
shared_ptr kopyasi               aynen
kullanim sayaci: 2
shared_ptr tasindi               bos
zayif referans erisimi: once evet, sonra hayir
yalniz std::move sonrasi p       aynen
```

**KY1.** İlk on üç satır bu kursun ölçtüğü şeyi tanımlar: on bir farklı işlem, hepsi tek bir soruya cevap verir — işlem bittiğinde kaynakta ne kaldı? Cevap dört sınıftan biridir: kaynak öncekiyle **aynen** aynı kalabilir, standardın belirttiği bir şekilde **değişti**rilmiş olabilir, standardın ya da sınıfın kendi garantisiyle **boş**alabilir, ya da standardın hiçbir şey söylemediği **belirsiz** bir duruma düşebilir. On bir işlemin dağılımı — 3 aynen, 3 değişti, 2 boş, 3 belirsiz — bu dersten kursun kapanışına kadar aynı kalacak; her ders bu on birin bir dilimini kendi konusuyla derinleştirecek.

#### Taşınan `unique_ptr`: Standardın Sözü Neyi Garanti Eder

Yukarıdaki çıktının ilk satırı `unique_ptr tasindi bos` diyor. Bu, `std::unique_ptr` için standardın açıkça verdiği bir sözdür: bir `unique_ptr` taşıyan yapıcıyla ya da taşıyan atamayla başka bir `unique_ptr`'a taşındığında, kaynak nesne `nullptr` tutar. Bu, akıllı işaretçinin (smart pointer) çıplak bir işaretçiden farkının tam da burada yattığını gösterir: çıplak bir işaretçi taşındıktan sonra ne tuttuğu konusunda hiçbir güvence vermez, `unique_ptr` verir.

Bu güvence yalnızca işaretçinin kendisi için geçerlidir, işaretçinin gösterdiği nesnenin içeriği için değil. Taşınmış bir `unique_ptr` üzerinde `*p` ile erişmeye çalışmak — `p` zaten `nullptr` olduğuna göre — boş bir işaretçiyi dereflemek olur ve bu tanımsız davranıştır (undefined behavior). Bu ders böyle bir çağrıyı ne yazar ne de çalıştırır; tanımlı karşılığı `if (p)` ile önce boşluğu sormaktır. `zayif_referans` işlevindeki `w.expired()` sorgusunun olumsuzu da aynı disiplinin bir başka örneğidir: elde tutulmayan bir kaynağa doğrudan erişmek yerine önce erişilebilir mi diye sorulur.

#### Tekil, Paylaşımlı ve Zayıf Sahiplik

`std::make_unique` ve `std::make_shared`, yukarıdaki kodda görüldüğü gibi akıllı işaretçi kurmanın standart yoludur — çıplak `new` çağrısını doğrudan yazmak yerine tercih edilirler. Üçünün taşıdığı sahiplik (ownership) farklıdır. `unique_ptr` tekil sahiplik (unique ownership) taşır: aynı anda yalnız bir `unique_ptr` bir nesneyi tutabilir, bu yüzden kopyalanamaz, yalnız taşınabilir. `shared_ptr` paylaşımlı sahiplik (shared ownership) taşır: birden çok `shared_ptr` aynı nesneyi gösterebilir, bir referans sayımı (reference count) kaç tanesinin gösterdiğini izler.

`paylasimli` işlevi bu farkı iki işlemle gösteriyor. Önce `kopya = p;` ile bir kopya açılıyor; çıktıdaki `shared_ptr kopyasi aynen` satırı, kaynağın (`p`) kopyalamadan sonra da aynı nesneyi göstermeye devam ettiğini söylüyor — `p.get()` değişmedi, `p` hâlâ `nullptr` değil. Hemen ardından basılan `kullanim sayaci: 2`, referans sayımının kopyalamayla birlikte arttığını doğruluyor. Sonra `auto tasindi = std::move(p);` çalışıyor; bu kez `shared_ptr tasindi bos` satırı, taşımanın kopyalamadan tamamen farklı bir sonuç verdiğini gösteriyor — kaynak artık `nullptr`.

**KY2.** Aynı sınıfın (`shared_ptr`) iki farklı işlemi iki farklı sınıfa düşüyor: kopyalama kaynağı **aynen** bırakıyor, taşıma **boş** bırakıyor. Bu, bu kursun ölçtüğü şeyin sınıfın kendisi değil, sınıf üzerinde çalıştırılan işlem olduğunu bir kez daha doğruluyor.

#### Zayıf Referans ve Döngüsel Referans

Üçüncü sahiplik türü zayıf sahipliktir (weak ownership): `std::weak_ptr`, bir nesneyi gösterir ama referans sayımını artırmaz, dolayısıyla nesnenin ömrünü uzatmaz. `zayif_referans` işlevi bunu gösteriyor: `w`, `p`'nin gösterdiği nesneye bir zayıf referans (weak reference) tutuyor; `p.reset()` çağrısından önce `w.expired()` yanlış (nesne hâlâ yaşıyor), çağrıdan sonra doğru (nesne artık yok). Çıktıdaki `zayif referans erisimi: once evet, sonra hayir` satırı tam olarak bunu gösteriyor.

Bir `weak_ptr`'ın var olma sebebi, `shared_ptr` ile kurulacak bir döngüsel referansın (reference cycle) önüne geçmektir. İki nesne birbirini `shared_ptr` ile tutarsa, ikisinin de referans sayımı hiçbir zaman sıfıra inmez ve ikisi de asla yok edilmez — bu bir bellek sızıntısıdır. Bu ders böyle bir döngüyü kurup çalıştırmaz; onun yerine tanımlı karşılığını çalıştırır: döngünün bir ucunu `shared_ptr`, diğer ucunu `weak_ptr` yaparak sızıntıyı baştan engellemek. `w.lock()` çağrısı — bu derste doğrudan kullanılmasa da — bir `weak_ptr`'dan güvenli biçimde geçici bir `shared_ptr` almanın standart yoludur; nesne hâlâ yaşıyorsa dolu, yok olduysa boş bir `shared_ptr` döner.

M11/K02'nin ölçtüğü "belleği kim sahipleniyor" sorusu, dillere göre farklı yanıtlanır. Rust'ta (M10/K02) bir değişken taşındığında derleyici o değişkeni artık kullanılamaz sayar ve onu kullanmaya çalışan kodu derlemeyi reddeder — taşınmış değişken bir daha okunamaz, kapsam sonunda onun için yıkım kodu da çalışmaz. C++'ta durum farklıdır: taşınan nesne yaşamaya devam eder, yıkıcısı yine çalışır; derleyici taşınmış bir nesneyi kullanmayı yasaklamaz, yalnızca hangi değeri taşıyacağı bu kursun dört sınıfıyla belirlenir. Sahiplik akıllı işaretçinin türüyle (tekil, paylaşımlı, zayıf) açıkça bildirilir, ama bu kursun kendisinin ölçtüğü şey sahipliğin kimde olduğu değildir — bir işlem bittiğinde kaynağın kendisinde ne kaldığıdır.

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

`yalniz_move` işlevi bu dersin en ince noktasını gösteriyor: `std::move` tek başına hiçbir şeyi taşımaz. `auto&& r = std::move(p);` satırı yalnızca `p`'yi bir sağ değere dönüştürür (bu dönüşümün adı ileride görülecek); herhangi bir taşıyan yapıcı ya da taşıyan atama çağırmaz, çünkü `r` bir referanstır, yeni bir `unique_ptr` inşa etmez. Çıktıdaki `yalniz std::move sonrasi p aynen` satırı bunu doğruluyor — `p.get()` hâlâ eskisiyle aynı, `p` hâlâ `nullptr` değil.

Kaynağı boşaltan `std::move` çağrısının kendisi değildir; kaynağı boşaltan, bir taşıyan yapıcının ya da taşıyan atamanın o dönüştürülmüş değer üzerinden gerçekten çağrılmasıdır. `akilli` işlevindeki `auto q = std::move(p);` satırı `p`'yi boşaltıyor çünkü bu satır yeni bir `unique_ptr` (`q`) inşa ediyor ve bu inşa `unique_ptr`'ın taşıyan yapıcıyı çağırıyor. `yalniz_move`'da böyle bir inşa yok; bu yüzden sonuç farklı sınıfa düşüyor. Bu ayrım, ilerleyen derste bir yazının (`std::move`) kendisinin bir tip dönüşümünden ibaret olduğu söylenirken tekrar karşımıza çıkacak.

#### Özet

- Bir işlemden sonra kaynakta ne kaldığı dört sınıfla adlandırılır: **aynen**, **değişti**, **boş**, **belirsiz**; bu kursun on bir dersi bu dört sınıfın farklı dilimlerini ölçer.
- Taşınan bir `unique_ptr`'ın kaynağı `nullptr` olur — bu standardın verdiği bir sözdür; taşınmış işaretçiyi dereflemek tanımsız davranıştır, tanımlı karşılığı `if (p)` sorgusudur.
- `shared_ptr` kopyalaması kaynağı **aynen** bırakır ve referans sayımını artırır; `shared_ptr` taşıması kaynağı **boş** bırakır — aynı sınıf, farklı işlem, farklı sonuç.
- `weak_ptr`, referans sayımını artırmadan bir nesneyi gösterir ve döngüsel referans sızıntısına karşı tanımlı bir karşılık sağlar.
- `std::move` tek başına hiçbir şeyi taşımaz; kaynağı yalnızca gerçekten çağrılan bir taşıyan yapıcı ya da taşıyan atama boşaltır — bu dersin sınırlayıcı ölçümü kaynağı **aynen** bırakan bir `std::move` çağrısıdır.

#### Sonraki Adım

Bu ders taşımanın sonucuna baktı: bir `unique_ptr` taşındığında kaynak boşalır, çünkü standart işaretçinin kendisi için bu sözü veriyor. Ama taşınan şey bir işaretçi değil de bir `std::string` gibi kendi içinde veri taşıyan bir tür olduğunda, standart aynı kesinlikte konuşmuyor. Sonraki ders, sağ değer, sol değer ve taşıyan yapıcı kavramlarını açarak standardın daha az kesin konuştuğu bu sonucu ölçüyor.

### Taşıma Semantiği

Source: https://academia.sh/tr/kurslar/cpp-standart-kutuphane/tasima-semantigi

std::move bir tip dönüşümünden ibarettir; asıl taşımayı yapan taşıyan yapıcı ve taşıyan atama işlecidir. Bir std::string taşındıktan sonra kaynakta ne kaldığını ölçme.

Önceki ders taşınan bir `unique_ptr`'ın kaynağını standardın verdiği bir sözle boşalttığını gösterdi: taşıma bitince kaynak `nullptr` olur, bu güvence akıllı işaretçinin kendi türünden gelir. Ama her tür aynı kesinlikte konuşmaz. `unique_ptr` yalnızca bir işaretçi taşıdığı için boşluğu ucuza garanti edebilir; `std::string` gibi kendi içinde veri taşıyan bir tür taşındığında standart aynı sözü vermez. Bu ders, standardın daha az kesin konuştuğu o sonucu ölçüyor.

#### Sağ Değer, Sol Değer, Sağ Değer Referansı

Bir ifadenin sol değer (lvalue) mi sağ değer (rvalue) mi olduğu, adının olup olmamasından çok, o ifadenin adresinin alınıp alınamayacağıyla ilgilidir. `s` gibi adlandırılmış bir değişken bir sol değerdir; `std::string("gecici")` gibi geçici bir sonuç ya da `std::move(s)`'nin döndürdüğü ifade bir sağ değerdir. Sağ değer referansı (rvalue reference), `T&&` yazımıyla bildirilir ve yalnızca sağ değerlere bağlanabilir; bu bağlanma, derleyiciye "bu değer artık kimsenin ihtiyaç duymadığı geçici bir değer, içeriğini alabilirsin" sinyalini verir.

`std::move` bir işlev gibi görünür ama aslında hiçbir veriyi taşımaz; yaptığı tek şey, elindeki bir sol değeri bir sağ değer referansına dönüştürmektir — düz bir `static_cast<T&&>` çağrısından ibarettir. Bu dönüşüm tek başına hiçbir taşıyan yapıcı ya da taşıyan atama çağırmaz; yalnızca derleyicinin aşırı yükleme çözümlemesinde sağ değere özgü sürümü (varsa) seçmesine izin verir.

Bu yüzden `std::move(s)` ifadesinin kendisi hiçbir yan etki üretmez: `s`'nin türü değişmez, adresi değişmez, içeriği değişmez. Yan etki, bu ifade bir yapıcıya ya da atama işlecine argüman olarak verildiğinde ve derleyici o çağrı için taşıyan sürümü seçtiğinde ortaya çıkar. `std::move(s)` bir işlev çağrısının içine hiç girmeden, örneğin yalnızca bir değişkene atanıp bir daha kullanılmadan da yazılabilir — bu durumda hiçbir taşıma gerçekleşmez, çünkü hiçbir taşıyan yapıcı ya da taşıyan atama çağrılmamıştır. Adın "taşı" olması, çağrının kendisinin bir taşımayı garanti ettiği anlamına gelmez; yalnızca bir isteği, bir izni ifade eder.

Bir sınıf hem `T(const T&)` hem de `T(T&&)` sürümünü birlikte bildirdiğinde, hangisinin çağrılacağına derleyici argümanın değer kategorisine bakarak karar verir: adlandırılmış bir sol değer verilirse `const T&` sürümü, `std::move` ile üretilmiş ya da zaten geçici olan bir sağ değer verilirse `T&&` sürümü seçilir. Bu seçim çağıran tarafın hiçbir ek işaret vermesini gerektirmez — aynı çağrı yazımı (`T yeni(kaynak);`), `kaynak`'ın türüne göre farklı bir yapıcıya yönlenir. `T&&` parametresi olmayan bir sınıfta ise her iki durumda da tek seçenek olan `const T&` sürümü çalışır; sağ değer verilmiş olması bu durumda hiçbir fark yaratmaz, çünkü derleyicinin seçebileceği başka bir aşırı yükleme yoktur.

#### Taşıyan Yapıcı ve Taşıyan Atama İşleci

Bir sınıfın taşıyan yapıcısı (move constructor) `T(T&& diger)` imzasıyla, taşıyan atama işleci (move assignment operator) `T& operator=(T&& diger)` imzasıyla bildirilir. İkisi de kaynaktaki kaynağı (bir arabellek, bir tutamaç, bir sayı) hedefe devretmek üzere yazılır; kopyalayan sürümlerinden farkı, yeni bir kaynak açmak yerine var olanı almalarıdır.

Aşağıdaki `Kutu` sınıfı bunu elle gösteriyor. Taşıyan yapıcı, `diger`'in `etiket_` üyesini kendi `etiket_` üyesine taşıyor — burada da `std::move` yalnızca `diger.etiket_`'i bir sağ değere çeviriyor, gerçek taşımayı `std::string`'in kendi taşıyan yapıcısı yapıyor. Taşıyan atama işleci aynı devri, zaten var olan bir nesnenin üzerine yazarak yapıyor. İkisi de `noexcept` işaretlidir; bu işaret yalnızca bir belge notu değildir — standart kütüphanenin `std::vector` gibi kapsayıcıları, yeniden ayırma sırasında elemanları taşıyıp taşıyamayacaklarına bu işarete bakarak karar verir. `noexcept` olmayan bir taşıyan yapıcı, kapsayıcıyı taşımak yerine kopyalamaya zorlayabilir.

```cpp
// kutu.cpp — tasiyan yapici, tasiyan atama ve kaynagin yikicisi
#include <cstdio>
#include <string>
#include <utility>
class Kutu {
public:
    explicit Kutu(std::string etiket) : etiket_(std::move(etiket)) {}
    Kutu(Kutu&& diger) noexcept : etiket_(std::move(diger.etiket_)) {
        std::printf("tasiyan yapici calisti\n");
    }
    Kutu& operator=(Kutu&& diger) noexcept {
        etiket_ = std::move(diger.etiket_);
        std::printf("tasiyan atama calisti\n");
        return *this;
    }
    Kutu(const Kutu&) = delete;
    Kutu& operator=(const Kutu&) = delete;
    ~Kutu() { std::printf("Kutu yikicisi calisti\n"); }
private:
    std::string etiket_;
};
static void tasiyan_islevler() {
    Kutu a("ilk etiket, kisa dize eniyilemesini asacak kadar uzun bir metin");
    Kutu b(std::move(a));
    Kutu c("ikinci etiket, yine kisa dize eniyilemesini asacak kadar uzun bir metin");
    c = std::move(b);
}
static void dize_tasima() {
    std::string s = "kucuk dize eniyilemesini asacak kadar uzun bir metin, tasima icin";
    std::string t = std::move(s);
    std::printf("t (kaynaktan gelen deger) = %s\n", t.c_str());
    s = "yeni deger";
    std::printf("s (yeni deger verildikten sonra) = %s\n", s.c_str());
}
static void sabit_dize() {
    const std::string c = "sabit bir metin, tasima yapicisina baglanamayacak kadar sabit";
    std::string once = c;
    std::string kopya = std::move(c);
    std::printf("kopya (c'den gelen deger) = %s\n", kopya.c_str());
    std::printf("c ayni mi: %s\n", (c == once) ? "evet" : "hayir");
}
int main() {
    tasiyan_islevler();
    dize_tasima();
    sabit_dize();
}
```

```text
tasiyan yapici calisti
tasiyan atama calisti
Kutu yikicisi calisti
Kutu yikicisi calisti
Kutu yikicisi calisti
t (kaynaktan gelen deger) = kucuk dize eniyilemesini asacak kadar uzun bir metin, tasima icin
s (yeni deger verildikten sonra) = yeni deger
kopya (c'den gelen deger) = sabit bir metin, tasima yapicisina baglanamayacak kadar sabit
c ayni mi: evet
```

**KY3.** `tasiyan_islevler` içindeki `Kutu` nesnelerinin hiçbiri, taşınmış olmaktan dolayı yıkıcısız kalmıyor: açılan her `Kutu` nesnesi kapsam sonunda kendi `Kutu yikicisi calisti` satırını basıyor. `a`'nın içeriği `b`'ye, `b`'nin içeriği `c`'ye taşındıktan sonra bile hepsi işlev sonunda normal biçimde yok ediliyor. Bu, önceki dersin M10/K02 ve M11/K02 karşılaştırmasını burada da doğruluyor: Rust'ta taşınan bir değişken derleyici tarafından artık kullanılamaz sayılır ve kapsam sonunda onun için hiçbir yıkım kodu çalışmaz; C++'ta taşınan nesne yaşamaya devam eder, yıkıcısı yine çağrılır — taşıma nesnenin ömrünü değil, yalnızca içeriğini etkiler.

Taşıyan atama işlecinin bir başka inceliği kendi kendine atamaya karşı davranışıdır: `x = std::move(x);` gibi bir çağrı yazılabilir olmalı ve nesneyi bozmamalıdır. `Kutu`'nun taşıyan atama işleci burada `etiket_ = std::move(diger.etiket_);` satırıyla `std::string`'in kendi taşıyan atamasına güveniyor; `std::string`'in kendi kendine taşıyan ataması güvenlidir: tanımsız davranışa yol açmaz ve nesneyi geçerli bırakır, ama standart dizgenin ardından hangi değeri taşıyacağını söylemez; sonuç bu dersin **belirsiz** sınıfına girer. Kaynağı elle yöneten bir sınıf (örneğin çıplak bir işaretçi tutan bir sınıf) yazarken bu kontrolü açıkça eklemek gerekir — aksi hâlde kendi kendine atama nesnenin kaynağını serbest bırakıp hemen ardından o serbest bırakılmış kaynağı okumaya çalışabilir.

#### Kaynağın Yıkıcısı Neden Hâlâ Çalışır

`tasiyan_islevler` çıktısındaki her `Kutu yikicisi calisti` satırı rastgele bir ayrıntı değil; C++'ın taşıma modelinin doğrudan bir sonucudur. Bir nesne taşındığında kapsam içinde kalmaya devam eder — ne yaşam süresi kısalır ne de yıkıcı çağrısı atlanır. `a` içeriğini `b`'ye devrettikten sonra bile `a` hâlâ geçerli bir `Kutu` nesnesidir; kapsam sonuna geldiğinde derleyici onun için de normal yıkıcı çağrısını üretir. Yıkıcının kendisi taşınmış ya da taşınmamış olmak arasında bir ayrım yapmaz — `~Kutu()` her zaman aynı gövdeyi çalıştırır, çünkü sınıf yazarı yıkıcıyı yalnızca bir kez, nesnenin olağan sonu için yazar.

Bu davranışın güvenli olmasının koşulu, taşıyan yapıcının ve taşıyan atamanın kaynağı yıkıcının güvenle çalışabileceği bir duruma bırakmasıdır. `Kutu` burada `etiket_` üyesini `std::string`'e devrediyor; `std::string`'in kendi taşıyan yapıcısı, kaynak dizeyi geçerli ama belirtilmemiş bir duruma getirir ve bu durumun kendi yıkıcısı tarafından güvenle serbest bırakılabileceğini garanti eder. Eğer `Kutu` bir `std::string` yerine çıplak bir `char*` tutsaydı, taşıyan yapıcının kaynaktaki işaretçiyi `nullptr`'a çekmesi gerekirdi — aksi hâlde hem kaynak hem hedef aynı belleği serbest bırakmaya çalışır ve program çift serbest bırakma hatasına düşerdi. Bir taşıyan yapıcı yazmanın örtük kuralı budur: kaynağı, o kaynağın yıkıcısı ikinci kez zarar vermeden çalışabilecek bir duruma getirmek.

Bu, Rust'ın modeliyle tam burada ayrılıyor. Rust'ta taşınan bir değişken için derleyici kapsam sonunda hiçbir yıkım kodu üretmez — değişken artık "canlı" sayılmadığı için düşürme (drop) çağrısı ona hiç uygulanmaz. C++'ta ise böyle bir muafiyet yoktur: taşınan nesne kapsamın sonuna kadar yaşamaya devam eder ve yıkıcısı, tıpkı hiç taşınmamış bir nesneninki gibi, mutlaka çalışır. Bu yüzden C++'ta taşıma sonrası kaynağın hangi sınıfa (**aynen**, **değişti**, **boş**, **belirsiz**) düştüğünü bilmek önemlidir — o nesne ortadan kalkmaz, kodun geri kalanı onunla (ya da en azından onun yıkıcısıyla) hâlâ karşılaşacaktır.

#### Taşınmış Bir `std::string`: Geçerli Ama Belirtilmemiş Durum

`dize_tasima` işlevindeki `std::string t = std::move(s);` satırından sonra `s`'nin içeriği hiçbir yerde okunmadan, doğrudan `s = "yeni deger";` ile üzerine yazılıyor. Bu sıralama rastgele değil: standart, taşınmış bir `std::string`'in "geçerli ama belirtilmemiş durumda" (valid but unspecified state) kalacağını söyler — nesne hâlâ geçerlidir, üzerinde `size()` çağırmak ya da yeni bir değer atamak güvenlidir, ama o an ne tuttuğu standart tarafından tanımlanmaz. Bu yüzden bu ders `s`'nin taşıma sonrası içeriğini hiçbir satırda basmıyor; yalnızca yeni bir değer verildikten sonraki hâlini okuyor.

`t`'nin değeri ise böyle değil: hedef nesnenin taşıma sonrası içeriği standart tarafından tam olarak tanımlıdır — kaynağın taşımadan önceki değeriyle aynı olacaktır. Çıktıdaki `t (kaynaktan gelen deger) = ...` satırı bu yüzden güvenle basılabiliyor; **belirsiz** sınıfına düşen yalnızca kaynak, hedef değil.

**KY4.** Taşımanın kopyalamadan ucuz olmasının nedeni de burada gizli: `std::string`'in taşıyan yapıcısı `t`'nin arabellek işaretçisini `s`'den devralır, karakterleri tek tek kopyalamaz. Bir kopyalayan yapıcı çağrılsaydı, kaynaktaki her karakter için yeni bir arabellek ayrılır ve o arabelleğe kaynaktan tek tek veri yazılırdı — dizenin uzunluğuyla orantılı bir işlem. Taşıyan yapıcı ise yalnızca birkaç üyeyi (arabellek işaretçisi, uzunluk, kapasite) hedefe kopyalar ve kaynağınkini sıfırlar; bu, dizenin içeriğinin ne kadar uzun olduğundan bağımsız, sabit maliyetli bir işlemdir. Bu ders bu devri saymaz — kaç karakterin taşındığı ya da kaç bayt kopyalanmadığı bu dersin ölçtüğü şey değil; ölçülen tek şey, işlem bitince kaynağın hangi sınıfa düştüğü.

Önceki kursun ölçtüğü "hangi nesne" sorusu (M11/K04) burada geçerli değil: orada `kopya` sınıfı bir hedefin adını taşıyordu, burada `aynen` bir kaynağın kendisinin adını taşıyor. Bir taşıma, hedefte her zaman kaynağın eski değerini kurar — hedef sınıfı buradaki ölçümün konusu değildir; ölçülen, geride kalan kaynaktır.

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

`sabit_dize` işlevi bu dersin sınırını gösteriyor: `c` bir `const std::string` olduğu için `std::move(c)` çağrısı `const std::string&&` türünde bir sağ değer referansı üretir. `std::string`'in taşıyan yapıcısı `std::string&&` (sabit olmayan) bir parametre bekler; `const std::string&&` ona bağlanamaz. Derleyici bu durumda aşırı yükleme çözümlemesinde geriye düşer ve kopyalayan yapıcıyı çağırır. Sonuç: `c` bir taşıma gibi görünen bir çağrıdan sonra bile **aynen** kalır — çıktıdaki `c ayni mi: evet` satırı bunu doğruluyor.

Bu, `std::move` yazmanın bir taşımayı garanti etmediğini gösteren en net örnektir. `std::move` yalnızca bir tip dönüşümüdür; hangi yapıcının gerçekten çağrılacağına derleyicinin aşırı yükleme çözümlemesi karar verir, ve bir `const` nitelemesi bu çözümlemeyi taşıyan yapıcıdan uzaklaştırabilir. Pratikte bu, `const` bir değişkeni bir işleve taşımak niyetiyle geçirmenin genellikle bir tasarım hatası olduğu anlamına gelir: değişken `const` olarak kaldığı sürece derleyicinin taşıyan sürümü seçmesi mümkün değildir, kod her zaman kopyalayan sürüme düşer — bu yüzden bir kaynağın gerçekten taşınması isteniyorsa o kaynağın `const` olmaması gerekir.

#### Özet

- Sol değer adlandırılmış, adresi alınabilen bir ifadedir; sağ değer geçici bir sonuçtur; sağ değer referansı (`T&&`) yalnızca sağ değerlere bağlanır.
- `std::move` bir tip dönüşümüdür, kendisi hiçbir veriyi taşımaz — hangi yapıcının çağrılacağına aşırı yükleme çözümlemesi karar verir.
- Taşınmış bir `std::string`'in kaynağı geçerli ama belirtilmemiş durumdadır (**belirsiz** sınıfı); bu ders onun içeriğini okumadan önce ona her zaman yeni bir değer verdi.
- Taşınan nesnenin yıkıcısı normal biçimde çalışmaya devam eder — C++'ta taşıma ömrü bitirmez, yalnızca içeriği devreder.
- `const` bir kaynak üzerinde `std::move` çağırmak taşıyan yapıcıya değil kopyalayan yapıcıya düşer; kaynak bu durumda **aynen** kalır — bu dersin sınırlayıcı ölçümü budur.

#### Sonraki Adım

Bu ders bir taşımanın gerçekleşip gerçekleşmediğinin, `std::move` yazmakla değil hangi yapıcının fiilen çağrıldığıyla belirlendiğini gösterdi. Bir sınıfın taşıyan yapıcısının derleyici tarafından örtük olarak yazılıp yazılmayacağı da benzer bir çözümlemeye bağlıdır — ve bu çözümlemeyi sınıfın yazdığı başka bir özel üye işlev bile bozabilir. Sonraki ders, bir yıkıcı yazmanın örtük taşıyan yapıcıyı nasıl bastırdığını ve sıfır kuralıyla beş kuralının bunu nasıl yönettiğini ölçüyor.

### Sıfır ve Beş Kuralı

Source: https://academia.sh/tr/kurslar/cpp-standart-kutuphane/sifir-ve-bes-kurali

Bir yıkıcı yazmanın örtük taşıyan yapıcıyı nasıl bastırdığı, beş özel üye işlevin birbirine bağlılığı ve sıfır kuralının bu bağı hiç kurmadan sonuca ulaşması.

Önceki ders `std::move`'un tek başına bir taşıma garantilemediğini, gerçek kararı aşırı yükleme çözümlemesinin verdiğini gösterdi: `const` bir kaynak taşıyan yapıcıya bağlanamadığı için kopyalayan yapıcıya düşüyordu. Aynı düşüş, `const` olmayan bir kaynak için de yaşanabilir — bu kez nedeni tür niteleyicisi değil, sınıfın kendi yazdığı başka bir özel üye işlevdir. Bu ders, bir yıkıcı yazmanın örtük taşıyan yapıcıyı nasıl sessizce ortadan kaldırdığını ölçüyor.

#### Yıkıcı Yazmak, Taşıyan Yapıcıyı Bastırır

```cpp
// kayit.cpp — yikici yazmanin ortuk tasiyan yapiciyi nasil bastirdigi
#include <cstdio>
#include <string>
#include <utility>
struct Kayit {
    std::string ad;
    ~Kayit() {}
};
static void yikicili_sinif() {
    Kayit a{"yikicisi yazilmis bir sinifin uzun adi, kisa dize eniyilemesini asan"};
    std::string once = a.ad;
    Kayit b = std::move(a);
    std::printf("b.ad = %s\n", b.ad.c_str());
    std::printf("a.ad ayni mi: %s\n", (a.ad == once) ? "evet" : "hayir");
}
struct Paket {
    std::string icerik;
    explicit Paket(std::string i) : icerik(std::move(i)) {}
    ~Paket() = default;
    Paket(const Paket&) = default;
    Paket& operator=(const Paket&) = default;
    Paket(Paket&&) = default;
    Paket& operator=(Paket&&) = default;
};
static void bes_default() {
    Paket a("acikca istenen bes islevle tasinan bir paketin icerigi, uzun tutuldu");
    Paket b = std::move(a);
    (void)b;
    std::printf("bes islev acikca istendi, tasima calisti\n");
}
struct SadeKayit {
    std::string ad;
};
static void sifir_kurali() {
    SadeKayit a{"sifir kuraline uyan sade bir kaydin uzun adi, tasima icin"};
    SadeKayit b = std::move(a);
    std::printf("b.ad = %s\n", b.ad.c_str());
    a.ad = "yeni deger";
    std::printf("a.ad (yeni deger verildikten sonra) = %s\n", a.ad.c_str());
}
int main() {
    yikicili_sinif();
    bes_default();
    sifir_kurali();
}
```

```text
b.ad = yikicisi yazilmis bir sinifin uzun adi, kisa dize eniyilemesini asan
a.ad ayni mi: evet
bes islev acikca istendi, tasima calisti
b.ad = sifir kuraline uyan sade bir kaydin uzun adi, tasima icin
a.ad (yeni deger verildikten sonra) = yeni deger
```

**KY5.** `Kayit`'in tek yazılmış özel üye işlevi yıkıcıdır — boş bir gövdeyle bile olsa, kullanıcı bir yıkıcı yazdığı an derleyici artık örtük bir taşıyan yapıcı ve örtük bir taşıyan atama üretmez. `Kayit b = std::move(a);` satırı bu yüzden taşıyan bir yapıcı bulamaz; aşırı yükleme çözümlemesi geriye, hâlâ örtük olarak var olan kopyalayan yapıcıya düşer. Sonuç, `a.ad`'nin taşımadan sonra da **aynen** kalmasıdır — çıktıdaki `a.ad ayni mi: evet` satırı bunu doğruluyor. `std::move(a)` burada da yazıldı, ama çağrılan şey bir taşıma değil bir kopyalamaydı.

Bu, önceki kursun ölçtüğü sorudan (M11/K04: bir ifade hangi nesneyle çalışıyor) farklı bir sonuca varıyor gibi görünse de aslında aynı ayrımı tekrarlıyor: K04'te `kopya` sınıfı bir hedefin yeni bir nesne açtığını, buradaki `aynen` ise kaynağın kendisinin değişmediğini adlandırıyor. `Kayit b = std::move(a);` satırı K04'ün ölçtüğü anlamda bir kopyalama işlemidir (`b` yeni bir nesnedir), ama bu kursun ölçtüğü anlamda kaynağın (`a`'nın) durumu **aynen**dir — iki ölçüm aynı satırı iki farklı eksende değerlendiriyor.

#### Beş Özel Üye İşlev ve Beş Kuralı

Bir sınıfın beş özel üye işlevi vardır: yıkıcı, kopyalayan yapıcı, kopyalayan atama, taşıyan yapıcı, taşıyan atama. Bunlardan biri elle yazıldığında derleyicinin diğerleri için ürettiği örtük sürümler değişebilir ya da tamamen ortadan kalkabilir — yukarıdaki `Kayit` örneği tam olarak bunu gösterdi. Beş kuralı (rule of five) bu bağımlılığı bir disipline çevirir: bu beşten biri elle yazılıyorsa, geri kalan dördü de düşünülmeli, gerekiyorsa açıkça yazılmalı ya da açıkça istenmelidir.

Beşinin her biri farklı bir anda, farklı bir amaçla çalışır. Yıkıcı (`~T()`) nesnenin ömrü bittiğinde, tuttuğu her kaynağı serbest bırakmaktan sorumludur; bir sınıf çıplak bir kaynak (dosya tanıtıcısı, işaretçi, kilit) tutuyorsa yıkıcı bu kaynağı geri vermenin tek garanti edilmiş yeridir. Kopyalayan yapıcı (`T(const T&)`) yeni bir nesneyi var olan bir nesnenin kopyası olarak kurar; kaynağı paylaşmaz, kendi bağımsız kopyasını açar. Kopyalayan atama (`T& operator=(const T&)`) aynı kopyalama işini, zaten var olan bir hedef nesnenin üzerine yazarak yapar — bu nedenle önce hedefin eski kaynağını bırakması, sonra yenisini kurması gerekir. Taşıyan yapıcı (`T(T&&)`) ve taşıyan atama (`T& operator=(T&&)`) ise kopyalamak yerine kaynağı devralır: yeni bir kaynak açmadan, kaynaktakini hedefe taşır ve kaynağı yıkıcının güvenle çalışabileceği bir duruma bırakır. Bu beşi arasındaki bağ tam olarak buradan gelir — biri diğerinin varsayımı üzerine kuruludur: taşıyan yapıcı, yıkıcının kaynağı nasıl serbest bıraktığını bilmeden kaynağı güvenle boşaltamaz; kopyalayan atama, yıkıcının hangi kaynağı bıraktığını bilmeden eskisini bırakamaz. Bir sınıf bu beşten yalnızca birini kendi eliyle yazdığında, derleyici artık "geri kalanlar da aynı varsayımlarla mı yazıldı" sorusuna güvenle evet diyemez ve bazılarını üretmeyi durdurur.

`Paket` sınıfı bu isteği `= default` ile açıkça yapıyor: yıkıcı, kopyalayan yapıcı, kopyalayan atama, taşıyan yapıcı, taşıyan atama — beşi de `= default` ile derleyiciden istendi. Burada derleyicinin doldurması gereken bir boşluk yoktur: `Paket`'in kendi yazılmış yapıcısı (`explicit Paket(std::string i)`) yalnız örtük **varsayılan** yapıcının (parametresiz `Paket()`) üretilmesini engeller; öteki beş özel üye işlev, `= default` yazılmasaydı da aynı biçimde üretilirdi. `= default` sonucu değiştirmez, niyeti kodda görünür kılar: okur beş işlevin var olduğunu ve derleyicinin ürettiği hâliyle çalıştığını dosyayı taramadan görür. Çıktıdaki `bes islev acikca istendi, tasima calisti` satırı, taşıyan yapıcının burada gerçekten çağrıldığını (ve derleyicinin ürettiği hâliyle `icerik` üyesini taşıdığını) belirtiyor.

Bu kural, K04'ün üç kuralına (rule of three — bir kopyalayan yapıcı, bir kopyalayan atama, bir yıkıcı birbirine bağlıdır) taşıma işlevlerini de ekleyerek genişletilmiş hâlidir; C++11 öncesinde yalnız üç işlev birbirine bağlıyken, taşıma işlevlerinin eklenmesiyle bağ beşe çıktı. K04'teki gerekçe şuydu: bir sınıf kendi yıkıcısını yazıyorsa (genellikle çıplak bir kaynağı serbest bırakmak için), derleyicinin ürettiği örtük kopyalayan yapıcı ve kopyalayan atama o kaynağı üye üye kopyalar — iki nesne aynı kaynağı paylaşır ve ikisi de kendi yıkıcısında onu serbest bırakmaya çalışır, bu da çift serbest bırakmayla sonuçlanır. Taşıma işlevleri eklendiğinde aynı gerekçe onlara da uygulanır: örtük bir taşıyan yapıcı, kaynağı kaynaktan hedefe kopyalayıp kaynakta da bırakırsa (taşıyan yapıcıya özgü sıfırlama yapılmazsa) aynı çift serbest bırakma riskini taşır. Beş kuralı bu yüzden üç kuralının bir devamıdır, ayrı bir kural değildir: aynı "elle yazılmış bir özel üye işlev, diğerlerinin örtük varsayımını bozar" ilkesinin taşımayı da kapsayacak şekilde genişletilmiş hâlidir.

#### Bastırma İki Yönde Aynı Şekilde İşlemez

`Kayit` örneği bir yönü gösterdi: yıkıcı yazmak, taşıma işlevlerinin **üretilmesini** engeller — taşıyan yapıcı ve taşıyan atama yoktur, sanki hiç bildirilmemiş gibi. Aşırı yükleme çözümlemesi bu yokluğu görür ve hâlâ örtük olarak üretilen kopyalayan yapıcıya döner; bu yüzden `Kayit b = std::move(a);` derlenir ve çalışır, sadece bir kopyalama yapar.

Ters yön farklı işler. Bir sınıf taşıyan yapıcısını ya da taşıyan atamasını elle yazarsa, derleyici kopyalayan yapıcıyı ve kopyalayan atamayı örtük olarak **üretmez**, üstelik onları `= delete` ile açıkça silinmiş sayar. Böyle bir sınıfı kopyalamaya çalışan kod derlenmez; aşırı yükleme çözümlemesi bir yedek bulup sessizce ona düşmez, hata verir. `Paket`'in beşini de `= default` ile açıkça istemesinin nedeni tam olarak budur: yalnız taşıma işlevlerini yazıp kopyalamayı kendi hâline bırakmış olsaydı, `Paket`'i kopyalamaya çalışan her kod derleme zamanında reddedilirdi — bu, sınıfın niyet ettiği bir sonuç olabilir de olmayabilir de, ama sessizce olması istenmez.

Bu iki yönün asimetrisi rastgele değil, bir güvenlik tercihidir. Derleyici bir taşıma işlevi yazıldığını gördüğünde, sınıf yazarının kaynağı yönetme biçimi hakkında özel bir şey düşündüğünü varsayar — belki sınıf, birden fazla nesnenin aynı kaynağı paylaşmasını anlamsız ya da tehlikeli buluyordur. Böyle bir sınıf için örtük bir kopyalayan yapıcı üretmek (kaynağı üye üye kopyalayarak) sınıfın niyetini sessizce çiğneyebilir; iki nesnenin aynı çıplak kaynağı tutup ikisinin de onu serbest bırakmaya çalışması gibi bir hataya yol açabilir. Derleyici bu riski almak yerine kopyalamayı tamamen kapatır ve sınıf yazarının açıkça karar vermesini bekler. Yıkıcı yazıldığında ise aynı varsayım geçerli değildir — bir yıkıcı yazmak tek başına "bu sınıf taşınamaz" anlamına gelmez, bu yüzden derleyici kopyalamayı kapatmaz, yalnızca taşımayı örtük üretmekten vazgeçer ve daha güvenli bulduğu kopyalamaya düşer.

#### Sıfır Kuralı

Sıfır kuralı (rule of zero) beş kuralının tam tersi bir yoldan aynı hedefe varır: sınıf hiçbir özel üye işlevi elle yazmaz, kaynağı zaten kendi beşini doğru yöneten üyelere (`std::string`, `std::unique_ptr`, `std::vector` gibi) bırakır. `SadeKayit` bunu gösteriyor: tek üyesi bir `std::string`, kendi hiçbir özel üye işlevi yok. Derleyici, üyesinin taşıyan yapıcısını çağıran bir taşıyan yapıcıyı örtük olarak üretir — ve bu üretilen taşıyan yapıcı gerçek bir taşıma yapar.

Sıfır kuralının pratikte tercih edilmesinin nedeni budur: `Paket`'in beş `= default` satırı hiçbir yeni davranış eklemez, yalnızca derleyicinin zaten üreteceği şeyi açıkça ister — bu satırları hiç yazmadan `icerik` üyesini `std::string` yerine doğrudan başka bir kaynak yönetici üyeye (örneğin `std::unique_ptr<std::string>`) bırakmak, aynı beş işlevi derleyiciden örtük olarak, hiçbir satır yazmadan almanın bir yolu olurdu. `Paket` burada beşi elle istemek zorunda kaldı çünkü kendi yazdığı bir yapıcısı (`explicit Paket(std::string i)`) vardı; sıfır kuralına uyan bir sınıf, kendi hiçbir yapıcısını da elle yazmayan bir sınıftır. Bir sınıfın kaynak yönetimini üyelerine devretmesi, hem daha az kod hem de beş işlev arasındaki bağımlılığı elle doğru kurma riskini tamamen ortadan kaldırması bakımından beş kuralına göre daha güvenli kabul edilir.

**KY6.** Ama gerçek bir taşımanın sonucu, `Kayit`'inkinden farklı bir sınıfa düşüyor. `b.ad` kaynağın eski değerini alıyor (hedefin değeri her zaman tanımlıdır), ama `a.ad` artık `std::string`'in kendi taşıma sonrası kuralına tabi: geçerli ama belirtilmemiş durumda, yani **belirsiz**. Bu ders `a.ad`'yi okumadan önce ona `"yeni deger"` atıyor — çıktıdaki son satır bu atamadan sonraki değeri gösteriyor, taşıma sonrası ham içeriği değil.

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

Bu üç örnek yan yana konduğunda görülen şey şu: aynı görünen bir satır (`X b = std::move(a);`) sınıfın yazılışına göre üç farklı sonuca varabiliyor. `Kayit` için sonuç **aynen** (kopyalayan yapıcıya düşüldüğü için), `SadeKayit` için sonuç **belirsiz** (gerçek bir taşıma, ama üyenin kendi geçerli ama belirtilmemiş durum kuralına tabi). Yıkıcıyı silmek — `Kayit`'ten yıkıcıyı çıkarmak — sınıfı `SadeKayit`'in durumuna, yani aynen'den belirsiz'e taşır. Hangisinin istendiği bir doğru-yanlış meselesi değildir; sınıfın taşınabilir mi taşınamaz mı, taşındığında ne kadar ucuz olması gerektiği gibi sorulara sınıfı yazan kişinin vereceği bir karardır. Bu ders o kararın hangi yöne düştüğünü göstermiyor, yalnızca kararın sonucunun nasıl gözlemleneceğini gösteriyor.

Bu geçişin somut mekaniği şudur: `Kayit`'in gövdesi boş bir yıkıcı dışında `SadeKayit`'ten farksızdır — ikisi de tek üye olarak bir `std::string` tutar. Aradaki tek fark, `Kayit`'in yıkıcıyı elle yazmış, `SadeKayit`'in hiç yazmamış olmasıdır. Bu tek satırlık fark, `X b = std::move(a);` çağrısının hangi yapıcıya düştüğünü tamamen değiştirir: `SadeKayit`'te derleyici taşıyan yapıcıyı üretir, kaynak (`a.ad`) gerçekten taşınır ve **belirsiz** sınıfına düşer; `Kayit`'te taşıyan yapıcı hiç üretilmez, çağrı kopyalayan yapıcıya düşer ve kaynak **aynen** kalır. Sınıfa yıkıcı eklemek ya da çıkarmak, başka hiçbir satır değişmeden, aynı çağrının sonucunu aynen'den belirsiz'e ya da belirsiz'den aynen'e çevirebilir — bu ders boyunca izlenen dört sınıf arasında bir sınıf değişikliğinin bu kadar dolaylı bir yoldan (bir işlevi eklemek/çıkarmak, taşımayla hiç ilgisi olmayan bir işlevmiş gibi görünse de) tetiklenebildiği tek örnek budur.

#### Özet

- Bir yıkıcı elle yazmak, derleyicinin örtük taşıyan yapıcı ve taşıyan atama üretmesini engeller; bu durumda taşıma çağrıları kopyalayan sürüme düşer ve kaynak **aynen** kalır.
- Beş özel üye işlev (yıkıcı, kopyalayan yapıcı, kopyalayan atama, taşıyan yapıcı, taşıyan atama) birbirine bağlıdır; beş kuralı bunlardan biri yazıldığında hepsinin gözden geçirilmesini, gerekirse `= default` ile açıkça istenmesini önerir.
- Sıfır kuralı hiçbir özel üye işlevi yazmadan, kaynağı zaten doğru yöneten üyelere (`std::string` gibi) bırakır; bu durumda derleyicinin ürettiği taşıyan yapıcı gerçek bir taşıma yapar.
- Sıfır kuralına uyan bir sınıfın üyesi taşındıktan sonra kaynak **belirsiz** sınıfına düşer — okumadan önce yeni bir değer atanmalıdır.
- Aynen ile belirsiz arasındaki seçim sınıf yazarının kararıdır; bu dersin sınırlayıcı ölçümü aynı satırın (`X b = std::move(a);`) sınıfın yazılışına göre iki farklı sınıfa düşebildiğini gösterir.

#### Sonraki Adım

Bu ders, bir kaynağın taşıma sonrası boş kalmasının standardın verdiği bir söz olabileceği gibi (`unique_ptr` örneğinde olduğu gibi) sınıfın kendi elle yazdığı bir davranış da olabileceğini örtük biçimde gösterdi. Sonraki ders bu ikinci yolu açık biçimde ele alıyor: kendi taşıyan yapıcısını yazan bir sınıfın kaynağı, standardın değil sınıfın kendisinin verdiği bir sözle boşalır.

### Kopyalanamaz ve Taşınamaz Tipler

Source: https://academia.sh/tr/kurslar/cpp-standart-kutuphane/kopyalanamaz-ve-tasinamaz-tipler

= delete ile bir sınıfın kopyalanmasını, taşınmasını ya da ikisini birden yasaklamak; kaynağın standarttan değil sınıfın kendisinden gelen bir sözle boşalması.

Önceki ders, bir kaynağın taşıma sonrası durumunun bazen standardın (`std::string`), bazen de sınıfın kendisinin (`Kayit`, `SadeKayit`) belirlediğini gösterdi. Bu ders son sınıfa, **boş**a, odaklanıyor: standardın `unique_ptr` için verdiği nullptr sözü gibi, bir sınıfın kendi taşıyan yapıcısının da kaynağı açıkça ve öngörülebilir biçimde boşaltabileceğini. Ayrıca `= delete`'in kopyalama ile taşımayı birbirinden bağımsız olarak nasıl kapatabildiğini ele alıyor.

Buraya kadarki üç ders bir sınıfın kopyalanabilir ve taşınabilir olduğunu varsaydı; soru yalnızca taşıma sonrası kaynağın hangi sınıfa düştüğüydü. Ama bazı sınıflar için kopyalama ya da taşımanın kendisi hiç anlamlı değildir — bir dosya tanıtıcısını kopyalamak iki bağımsız kapatma çağrısına, bir kilidin sahipliğini paylaşmak birden fazla ipin aynı kilidi tuttuğunu sanmasına yol açabilir. Bu ders, bir sınıf yazarının kopyalamayı, taşımayı ya da ikisini birden tamamen kapatmak istediğinde kullandığı araç olan `= delete`'i ve bu kapatmanın üç farklı bileşimini (kopyalanamaz-ama-taşınabilir, kopyalanabilir-ama-taşınamaz, ikisi de değil) ele alıyor.

#### `= delete` ile Kısıtlı Sahiplik

```cpp
// tutamac.cpp — kismen silinmis ozel uye islevlerin turden turedigi haller
#include <cstdio>
#include <type_traits>
#include <utility>
class Tutamac {
public:
    explicit Tutamac(int no) : no_(no) {}
    Tutamac(const Tutamac&) = delete;
    Tutamac& operator=(const Tutamac&) = delete;
    Tutamac(Tutamac&& diger) noexcept : no_(diger.no_) {
        diger.no_ = -1;
    }
    Tutamac& operator=(Tutamac&&) = delete;
    int no() const { return no_; }
private:
    int no_;
};
static void tutamac_ozellikleri() {
    std::printf("Tutamac kopyalanabilir mi: %s\n", std::is_copy_constructible_v<Tutamac> ? "evet" : "hayir");
    std::printf("Tutamac tasima-atanabilir mi: %s\n", std::is_move_assignable_v<Tutamac> ? "evet" : "hayir");
    std::printf("Tutamac tasima-yapilabilir mi: %s\n", std::is_move_constructible_v<Tutamac> ? "evet" : "hayir");
}
static void tutamac_tasima() {
    Tutamac a(7);
    Tutamac b(std::move(a));
    std::printf("b.no() = %d\n", b.no());
    std::printf("a.no() (tasimadan sonra) = %d\n", a.no());
}
class SadeceKopya {
public:
    explicit SadeceKopya(int deger) : deger_(deger) {}
    SadeceKopya(const SadeceKopya&) = default;
    SadeceKopya& operator=(const SadeceKopya&) = default;
    SadeceKopya(SadeceKopya&&) = delete;
    SadeceKopya& operator=(SadeceKopya&&) = delete;
private:
    int deger_;
};
static void sadece_kopya_ozellikleri() {
    std::printf("SadeceKopya kopyalanabilir mi: %s\n", std::is_copy_constructible_v<SadeceKopya> ? "evet" : "hayir");
    std::printf("SadeceKopya tasima-yapilabilir mi: %s\n", std::is_move_constructible_v<SadeceKopya> ? "evet" : "hayir");
}
class Kilit {
public:
    Kilit() = default;
    Kilit(const Kilit&) = delete;
    Kilit& operator=(const Kilit&) = delete;
private:
    int durum_ = 0;
};
static void kilit_ozellikleri() {
    std::printf("Kilit kopyalanabilir mi: %s\n", std::is_copy_constructible_v<Kilit> ? "evet" : "hayir");
    std::printf("Kilit tasima-yapilabilir mi: %s\n", std::is_move_constructible_v<Kilit> ? "evet" : "hayir");
}
int main() {
    tutamac_ozellikleri();
    tutamac_tasima();
    sadece_kopya_ozellikleri();
    kilit_ozellikleri();
}
```

```text
Tutamac kopyalanabilir mi: hayir
Tutamac tasima-atanabilir mi: hayir
Tutamac tasima-yapilabilir mi: evet
b.no() = 7
a.no() (tasimadan sonra) = -1
SadeceKopya kopyalanabilir mi: evet
SadeceKopya tasima-yapilabilir mi: hayir
Kilit kopyalanabilir mi: hayir
Kilit tasima-yapilabilir mi: hayir
```

**KY7.** `Tutamac` kopyalayan yapıcısını ve kopyalayan atamasını `= delete` ile açıkça kapatıyor — bu ikisini kullanmaya çalışan kod derlenmez. Taşıyan yapıcı ise elle yazılmış: kaynağın `no_` üyesini hedefe taşıyor ve kaynağa `-1` yazıyor. Bu, `unique_ptr`'ın nullptr sözünden farklı bir yerden geliyor: `unique_ptr`'ı boşaltan standardın kendisidir, `Tutamac`'ı boşaltan ise sınıfın yazarının seçtiği bir değerdir (`-1`). İkisi de **boş** sınıfına girer, ama sözü veren taraf farklıdır — biri standart, öbürü sınıf. Taşıyan atama ise ayrıca `= delete` ile kapatılmış: `Tutamac` taşıyan yapıcıyla inşa edilebilir ama taşıyan atamayla var olan bir nesneye aktarılamaz. Bu asimetri kasıtlı olabilir — örneğin `Tutamac` bir kez kurulduktan sonra yeniden atanmasının anlamsız olduğu bir kaynağı sarıyor olabilir.

`Tutamac`'ın kopyalanamaz ama taşınabilir olması rastgele bir bileşim değil; standart kütüphanenin `unique_ptr`'ının izlediği tam olarak aynı desendir. `unique_ptr` de kopyalayan yapıcısını ve kopyalayan atamasını `= delete` ile kapatır, taşıyan yapıcısını ve taşıyan atamasını ise elle yazar ve kaynağı (tuttuğu ham işaretçiyi) kaynaktan hedefe devredip kaynakta `nullptr` bırakır. `Tutamac`'ın `no_` üyesini `-1`'e çekmesi, `unique_ptr`'ın işaretçisini `nullptr`'a çekmesinin küçük ölçekli bir tekrarıdır: ikisi de "bu kaynağın sahibi aynı anda yalnızca bir nesne olabilir, ama sahiplik bir nesneden diğerine devredilebilir" kuralını, kopyalamayı tamamen yasaklayıp taşımayı açıkça izin vererek uyguluyor. Bir sınıf yazarı `unique_ptr` benzeri bir sahiplik modeli istediğinde kullandığı somut yöntem budur: kopyalayan işlevleri sil, taşıyan işlevleri elle yaz ve kaynağı kaynakta öngörülebilir bir değere çek.

#### Kopyalanabilir Ama Taşınamaz: Tersten Bir Kısıtlama

`SadeceKopya`, önceki dersin `Kayit`'inden farklı bir yoldan taşınamaz hâle geliyor. `Kayit` taşıma işlevlerini hiç bildirmiyordu; taşıma denendiğinde aşırı yükleme çözümlemesi sessizce kopyalayan yapıcıya düşüyordu, program derleniyor ve çalışıyordu. `SadeceKopya` ise taşıma işlevlerini **bildiriyor ve siliyor**. Aradaki fark, bir taşıma denendiğinde ortaya çıkıyor: silinmiş bir işlev aşırı yükleme çözümlemesine tam olarak katılır, adaydır; eğer en iyi eşleşen aday oysa, program derlenmez. `SadeceKopya`'nın bir nesnesini `std::move` ile ilklendirmeye çalışan bir satır bu yüzden `Kayit`'teki gibi sessizce kopyaya düşmez, doğrudan bir derleme hatası verir. Bu ders o satırı ayrı bir kod bloğuna koymuyor — çünkü derlenmeyen bir blok, bu kursun her bloğun gerçekten derlenip koşturulduğu kuralını ihlal eder — ama `is_move_constructible_v<SadeceKopya>`'nın `hayir` basması, bu yasağın gerçek olduğunun dolaylı kanıtıdır.

Buradaki ayrım şudur: taşıma işlevi hiç yoksa aşırı yükleme çözümlemesi geriye, var olan başka bir adaya (kopyalayan yapıcıya) düşer — sessiz ve derlenen bir sonuç. Taşıma işlevi var ama silinmişse, aşırı yükleme çözümlemesi onu en iyi eşleşen aday olarak seçer ve orada durur — gürültülü ve derlenmeyen bir sonuç. İkisi de "taşınamaz" olarak tarif edilebilir, ama biri programı sessizce farklı bir işleme yönlendirir, öbürü programı hiç derletmez.

`SadeceKopya`'nın kopyalanabilir-ama-taşınamaz bileşimi, `Tutamac`'ın tersidir ve genellikle farklı bir gerekçeyle seçilir: bir sınıf, değeri paylaşılabilir ama sahipliği devredilemez bir kaynağı (örneğin sabit bir yapılandırma değeri, ya da kopyalanması ucuz ama taşınmasının ekstra bir anlamı olmayan küçük bir veri) tutuyorsa, taşıma işlevlerini kapatıp yalnızca kopyalamaya izin vermek bilinçli bir tasarım kararı olabilir. Bu, `deger_` üyesinin bir `int` olduğu `SadeceKopya` örneğinde açıkça görülüyor: `int` taşınabilir bir türdür, sınıf taşımayı üye türünden değil kendi tercihinden dolayı kapatıyor. Bir sınıfın taşınabilirliği, tıpkı kopyalanabilirliği gibi, üyelerinin taşınabilirliğinden değil sınıfın kendi bildirdiği özel üye işlevlerden çıkar.

#### Ne Kopyalanabilir Ne Taşınabilir

Bazı türlerin kopyalanması da taşınması da anlamsızdır — bir kilidin sahipliğini kopyalamak iki bağımsız kilide, taşımak ise kilit tutan bir ipin kimin tuttuğunun karışmasına yol açabilir. Standart kütüphanenin `std::mutex`'i bu sınıfa örnektir (bu ders eşzamanlılığı öğretmiyor, yalnız adını anıyor): hem kopyalayan hem taşıyan işlevleri silinmiştir, ne kopyalanabilir ne taşınabilir.

Bu üç örnek — `Tutamac` (kopyalanamaz, taşınabilir), `SadeceKopya` (kopyalanabilir, taşınamaz), `std::mutex` ve `Kilit` (ne kopyalanabilir ne taşınabilir) — aynı soruya üç farklı yanıttır: bu tür için hangi sahiplik devri anlamlıdır? Bu yanıt hiçbir zaman çağıran koddan, derleme bayrağından ya da çalışma zamanı ayarından gelmez; sınıfın kendi tanımına, `= default` ve `= delete` satırlarına yazılır. Bir sınıfın kopyalanabilir mi, taşınabilir mi olduğunu öğrenmek için o sınıfı kullanan kodu okumak gerekmez — sınıfın kendi bildirimine bakmak yeterlidir. Bu, sahiplik kısıtlamalarının türde ilan edildiği anlamına gelir: kısıtlama bir belgede, bir yorumda ya da bir kullanım kuralında değil, derleyicinin denetleyebileceği bir bildirimde durur.

`Kilit` sınıfı burada daha ince bir noktayı gösteriyor: kopyalayan yapıcı elle `= delete` ile bildirilmiş, ama taşıma işlevlerinden hiçbiri hiç yazılmamış — ne `= default` ne `= delete`. Buna rağmen `is_move_constructible_v<Kilit>` `hayir` basıyor.

**KY8.** Bunun nedeni, taşıma işlevlerinin örtük üretilme kuralının kopyalayan yapıcının **var olup olmadığına**, silinmiş olup olmadığına değil, bakmasıdır: bir sınıf kopyalayan yapıcısını, kopyalayan atamasını ya da yıkıcısını kullanıcı olarak bildirirse — bu bildirim `= delete` bile olsa — derleyici artık taşıma işlevlerini örtük üretmez. `Kilit` taşımayı hiç ağzına almadı, ama kopyalayan yapıcıyı silerek dolaylı yoldan taşınamaz hâle geldi. Bu, **boş** ile **belirsiz** arasındaki ayrımı da netleştiriyor: her iki sınıfta da kaynaktan bir değer alınıp hedefe götürülür — kaynak taşımadan önceki değerini kaybeder, ikisi de bu bakımdan aynıdır. Ayrım, kaynağın geride bıraktığı değerin nereye vardığının söylenip söylenmediğidir. **Boş**, kaynağın taşımadan sonra alacağı değerin standart ya da sınıfın kendisi tarafından açıkça vaat edildiği, yani nereye vardığı söylenen durumdur (`Tutamac`'ın `-1`'i, kendi taşıyan yapıcısının bir sözü; `unique_ptr`'ın nullptr'ı, standardın bir sözü). **Belirsiz** ise böyle bir vaadin hiç verilmediği, kaynağın nereye vardığının söylenmediği durumdur (`std::string`'in taşınmış içeriği — geçerlidir ama hangi değeri tuttuğu belirtilmez). `Kilit`'in durumu ikisinden de değil — taşıma zaten hiç mümkün olmadığı için kaynağın "taşıma sonrası durumu" sorusu hiç sorulmuyor.

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

`Kilit`'in `durum_` adlı `int` üyesi taşınabilir bir türdür, sınıfın kendisi de başka hiçbir açık taşıma yasağı içermez — üye tabanlı akıl yürütmeyle "elbette taşınabilir" denebilirdi. Ama sonuç `hayir`. Bunun nedeni üyelerin türü değil, `Kilit`'in kendi bildirdiği tek bir özel üye işlevdir: silinmiş kopyalayan yapıcı. Bu ders boyunca görülen kural burada en sıkıştırılmış hâliyle ortaya çıkıyor: taşınabilirlik sorusu üyelere değil, sınıfın "büyük üçlü"sünden (yıkıcı, kopyalayan yapıcı, kopyalayan atama) herhangi birinin kullanıcı tarafından dokunulup dokunulmadığına bakar. Dokunulmuşsa — silinmiş olsa bile — taşıma örtük üretimden çıkar.

Bu kural, önceki dersteki `Kayit` örneğiyle aynı köktendir, ama sonucu tam tersidir. `Kayit` bir yıkıcı yazarak taşımayı örtük üretimden çıkarmıştı; aşırı yükleme çözümlemesi geriye, hâlâ örtük var olan kopyalayan yapıcıya düşüyordu — sonuç, sessizce çalışan bir kopyalama idi. `Kilit` ise kopyalayan yapıcıyı **silerek** taşımayı örtük üretimden çıkarıyor; ama bu kez geriye düşülecek bir kopyalayan yapıcı da yok, çünkü o da silinmiş durumda. Sonuç, ne kopyalanabilen ne taşınabilen bir sınıf. Aynı temel kural — kullanıcının bildirdiği bir özel üye işlev, diğerlerinin örtük üretimini bastırır — iki farklı sınıfta iki farklı yöne, birinde sessiz bir kopyalamaya, öbüründe tam bir kapanmaya varıyor. Bu, dördüncü dersin üçüncü dersle kurduğu doğrudan bağdır: aynı mekanizma, hangi işlevin dokunulduğuna göre bambaşka bir sonuç üretebiliyor.

Bu üç bileşim — `Tutamac`, `SadeceKopya`, `Kilit` — bu kursun dört dersi boyunca izlenen sorunun (bir işlem kaynağa ne bırakıyor) bir önceki katmanına, yani "bu işlem kaynak için hiç mümkün mü" sorusuna karşılık geliyor. Önceki üç ders taşımanın her zaman mümkün olduğunu varsayıp sonucunu ölçtü; bu ders taşımanın (ya da kopyalamanın) hiç mümkün olmayabileceğini, ve bu mümkün olmayışın da tıpkı bir taşımanın sonucu gibi sınıfın kendi bildirimine yazıldığını gösterdi. Bir sınıfın API'sini okuyan biri, `= default` ve `= delete` satırlarına bakarak o sınıfla hangi devirlerin yapılabileceğini, hangi kod deseninin derlenmeyeceğini, hangi kaynağın kaç nesne arasında paylaşılabileceğini önceden bilir — bunun için sınıfın belgesine ya da kullanım kılavuzuna bakmasına gerek yoktur.

#### Özet

- `= delete`, kopyalama ve taşımayı birbirinden bağımsız olarak kapatabilir; `Tutamac` taşıma-yapılabilir ama taşıma-atanamaz, kopyalanamaz bir bileşimi gösteriyor.
- Sınıfın kendi taşıyan yapıcısı bir kaynağı açıkça boşaltabilir (`Tutamac`'ın `-1`'i); bu, standardın verdiği bir sözden (`unique_ptr`'ın nullptr'ı) farklı bir kaynaktan gelen aynı **boş** sınıfıdır.
- Taşıma işlevi hiç bildirilmemişse aşırı yükleme çözümlemesi sessizce kopyalayan yapıcıya düşer (`Kayit`); taşıma işlevi bildirilip silinmişse aynı çağrı derleme hatası verir (`SadeceKopya`) — ikisi de "taşınamaz" ama sonuçları farklıdır.
- Kullanıcının bildirdiği bir kopyalayan yapıcı — silinmiş olsa bile — derleyicinin taşıma işlevlerini örtük üretmesini engeller; `Kilit` taşımayı hiç anmadan taşınamaz olur.
- Bazı türler (`std::mutex` gibi) ne kopyalanabilir ne taşınabilir olacak biçimde tasarlanır; bu, sahipliğin o türde hiçbir yolla devredilmemesi gerektiği anlamına gelir.

Bu üç deseni tanımak, bir üçüncü taraf kütüphanesinin ya da standart kütüphanenin bir türüyle karşılaşıldığında da işe yarar: `std::thread` kopyalanamaz ama taşınabilir, `std::ifstream` da öyle; `std::atomic` ise ne kopyalanabilir ne taşınabilir. Bu davranışların hiçbiri belgeye bakmadan tahmin edilemez, ama sınıfın bildirimindeki `= delete` ve `= default` satırları görülebiliyorsa oradan doğrudan okunur.

#### Sonraki Adım

Bu dört ders, tek bir nesnenin taşınmasının dört olası sonucunu (aynen, değişti, boş, belirsiz) ve bu sonucun nereden — standarttan mı, sınıftan mı, hiç mümkün olmayışından mı — geldiğini ölçtü. Bir sonraki konu bu soruyu tek bir nesneden çoğul bir yapıya taşıyor: bir kap (`container`) elemanlarını nasıl tutuyor, bir eleman kaptan alınıp başka bir kaba taşındığında kabın kendisi ve elemanları hangi sınıfa düşüyor.

## Standart Kütüphane

Kapsayıcılar, yineleyiciler ve geçerlilik kuralları, algoritmalar, sil–kaldır deyimi, lambdalar, aralıklar ve tip çıkarımı — her biri bir işlemin kaynağa ne bıraktığı sorusuyla birlikte.

### Kapsayıcılar

Source: https://academia.sh/tr/kurslar/cpp-standart-kutuphane/kapsayicilar

map'in operator[] ile okumasının anahtar yoksa eleman eklediğinin, find ve contains'in kaynağı aynen bıraktığının ölçümü.

Önceki ders `Kopyalanamaz ve Taşınamaz Tipler`'de bir sınıfın kendi taşıyan
yapıcısını yazarak taşınmış kaynağa hangi boş durumu bırakacağına karar
verdiğini gösterdi — `Tutamac`'ın numarası `-1` oluyordu, çünkü taşıyan
yapıcı öyle yazılmıştı. Bu ders standart kütüphanenin hazır kapsayıcılarına
geçiyor ve aynı soruyu bu kez bir **okuma** işlemine soruyor: `stok["armut"]`
yazmak, bir değeri okumak gibi görünüyor. Ama `"armut"` anahtarı haritada
yoksa bu satır kaynağa ne bırakıyor?

#### `operator[]` ile Okumak Aslında Yazmaktır

`std::map<std::string, int>::operator[]`, anahtar haritada zaten varsa ona
bağlı değeri döndürür. Anahtar yoksa farklı bir şey yapar: değeri sıfırla
kurulmuş yeni bir eleman **ekler** ve o elemana bir referans döndürür. Bu,
imzasından çıkarılamayan bir yan etkidir — `[]` köşeli parantezi çoğu
programcıya "sorgula" der, ama `map` için anlamı "varsa getir, yoksa kur"dur.

```cpp
// kapsayici-okuma.cpp — operator[] ile okuma haritaya eleman ekler mi
#include <cstdio>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> stok{{"elma", 4}};
    std::size_t once = stok.size();

    int adet = stok["armut"];
    (void)adet;

    std::printf("eleman sayisi degisti mi %s\n",
        (stok.size() != once) ? "evet" : "hayir");
    std::printf("armut degeri %d\n", stok["armut"]);
    return 0;
}
```

```text
eleman sayisi degisti mi evet
armut degeri 0
```

**KT1.** `stok["armut"]` çağrısı, `"armut"` haritada yokken haritayı
**değiştirdi**: eleman sayısı bir arttı ve yeni elemanın değeri `int`'in
varsayılan değeri olan `0` oldu. `adet` değişkenine atanan değer bir okuma
gibi görünse de, o okumanın kendisi haritaya yazdı. Bu ifadenin sınıfı
**değişti** — kaynak hâlâ bir değer taşıyor ve standart bu değeri söylüyor
(boş harita değil, bir elemanlı harita), ama değer işlemden öncekinden
farklı.

#### `find` ve `contains`: Aynı Soruyu Eklemeden Sormak

Bir anahtarın haritada olup olmadığını öğrenmenin `operator[]`'i çağırmayan
iki yolu vardır. `find`, anahtarı bulursa o elemana bir yineleyici, bulamazsa
`end()` döndürür — hiçbir durumda eleman eklemez. `contains`, C++20 ile gelen
daha doğrudan yol; yalnız bir `bool` döndürür.

```cpp
// kapsayici-sorgu.cpp — find ve contains haritayi degistirir mi
#include <cstdio>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> stok{{"elma", 4}};
    auto once = stok;

    auto it = stok.find("armut");
    bool var = stok.contains("kiraz");

    std::printf("find sonucu %s\n", (it == stok.end()) ? "bulunamadi" : "bulundu");
    std::printf("contains sonucu %s\n", var ? "evet" : "hayir");
    std::printf("harita degisti mi %s\n", (stok != once) ? "evet" : "hayir");
    return 0;
}
```

```text
find sonucu bulunamadi
contains sonucu hayir
harita degisti mi hayir
```

**KT2.** `find("armut")` ve `contains("kiraz")` ikisi de haritada olmayan
anahtarları sorguladı ve ikisi de haritayı **aynen** bıraktı: `stok`, işlem
öncesindeki kopyasıyla hâlâ eşit. `find`'ın döndürdüğü `end()` yineleyicisi
"burada yok" demenin bir yolu, eklemenin bir tetikleyicisi değil. Bir haritada
bir anahtarın varlığını yalnız denetlemek isteyen kod `operator[]` yerine
`find` ya da `contains` kullanmalı — aksi hâlde sorgu, sorguladığı yapıyı
değiştirir.

#### Kapsayıcı Aileleri ve Seçim Ölçütü

Standart kütüphane kapsayıcıları üç aileye ayrılır. **Sıralı kapsayıcılar**
(`vector`, `deque`, `list`, `array`) elemanın konumunu eklenme sırasından
alır — burada "sıralı" büyüklüğe göre dizilmiş anlamına gelmez, elemanın yeri
programcının onu koyduğu yerdir. `vector` bitişik bellekte durur ve sona
ekleme ortalama sabit zamanlıdır; sona erişim indeksle sabit zamanlıdır.
`list` çift yönlü bağlı bir listedir; ortadan ekleme sabit zamanlıdır ama
indeksle erişim yoktur, baştan gezilir.

**İlişkisel kapsayıcılar** (`map`, `set`) elemanları bir anahtara göre
sıralı tutar; genellikle dengeli bir ikili arama ağacıyla gerçeklenir ve
arama, ekleme, silme `O(log n)` karmaşıklıktadır. **Sırasız kapsayıcılar**
(`unordered_map`, `unordered_set`) elemanları bir özet tablosunda tutar;
ortalama durumda arama ve ekleme `O(1)`'dir, ama en kötü durumda bütün
elemanlar aynı kovaya düşerse `O(n)`'e düşer. Seçim, hangi işlemin sık
yapılacağına bağlıdır: sık sıralı gezinme gerekiyorsa `map`, yalnız hızlı
arama gerekiyor ve sıra önemsizse `unordered_map` tercih edilir.

```cpp
// aile-secimi.cpp — sirali, iliskisel ve sirasiz kapsayicidan okuma
#include <cstdio>
#include <map>
#include <unordered_map>
#include <vector>

int main() {
    std::vector<int> sirali{30, 10, 20};
    std::map<int, int> iliskisel{{1, 10}, {2, 20}};
    std::unordered_map<int, int> sirasiz{{1, 10}, {2, 20}};

    std::printf("vector ilk eleman %d\n", sirali.front());
    std::printf("map en kucuk anahtar %d\n", iliskisel.begin()->first);
    std::printf("unordered_map arama %d\n", sirasiz.at(1));
    return 0;
}
```

```text
vector ilk eleman 30
map en kucuk anahtar 1
unordered_map arama 10
```

**KT3.** Bu üç okuma da bir kapsayıcıyı **aynen** bıraktı: `front()`,
yineleyici üzerinden `first` okuması ve `at()` hiçbiri kaynağa yazmadı.
`unordered_map`'in elemanlarını hangi sırada tuttuğu burada hiç
sorgulanmadı ve sorgulanmayacak — sırasız kapsayıcının dolaşma sırası
gerçeklemeye bağlıdır ve bu kursta hiç basılmaz. `map`'te `begin()->first`
her koşumda `1` verir çünkü `map` anahtara göre sıralıdır; aynı çağrı bir
`unordered_map` üzerinde yapılsaydı sonuç güvenilir olmazdı.

#### `at()`: Sınır Denetimi Olan Erişim

`operator[]`'in bir sorunu daha var: `const` bir haritada kullanılamaz,
çünkü anahtar yoksa eleman ekleyebilme ihtimali onu değiştirici bir işlem
yapar. `at()` bu sorunu ortadan kaldırır — anahtar yoksa eleman eklemez,
bunun yerine `std::out_of_range` fırlatır ve `const` bir haritada da
kullanılabilir.

```cpp
// at-erisimi.cpp — at() sinir disi anahtarda ne yapar
#include <cstdio>
#include <map>
#include <stdexcept>
#include <string>

int main() {
    const std::map<std::string, int> stok{{"elma", 4}};

    try {
        int deger = stok.at("armut");
        (void)deger;
        std::printf("yakalandi mi hayir\n");
    } catch (const std::out_of_range&) {
        std::printf("yakalandi mi evet\n");
    }

    std::printf("eleman sayisi %zu\n", stok.size());
    return 0;
}
```

```text
yakalandi mi evet
eleman sayisi 1
```

**KT4.** `stok.at("armut")` bir istisna fırlattı ve bu ders yalnız
**yakalandığını** bastı — istisnanın `what()` metnini basmadı, çünkü o
metnin kesin biçimi kütüphane uygulamasına bağlıdır ve dersin ölçtüğü şey
değil. Fırlatmadan sonra `stok`'un eleman sayısı hâlâ `1`: harita **aynen**
kaldı. Bu, `at()`'i `const` bir kapsayıcı üzerinde güvenle kullanılabilir
kılan şey — sınır dışı bir anahtar isteği kaynağı hiç değiştirmiyor, yalnız
bunu bir istisnayla bildiriyor.

#### `insert` ve `emplace`: Var Olan Anahtara Dokunmama Kuralı

`operator[]` yalnız anahtar yoksa eleman ekleyip döndürmüyor; anahtar zaten
varsa değeri **değiştiriyor**. `insert` ve `emplace` bunun tam tersi bir
garanti taşır: anahtar zaten varsa hiçbir şeyi değiştirmezler ve mevcut
elemana dokunmadıklarını bir `bool` ile bildirirler.

```cpp
// insert-emplace.cpp — anahtar zaten varken insert kaynagi degistirir mi
#include <cstdio>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> stok{{"elma", 4}};

    auto sonuc = stok.insert({"elma", 99});
    bool eklendi = sonuc.second;

    std::printf("eklendi mi %s\n", eklendi ? "evet" : "hayir");
    std::printf("elma degeri %d\n", stok.at("elma"));
    return 0;
}
```

```text
eklendi mi hayir
elma degeri 4
```

**KT5.** `insert({"elma", 99})` çağrısı `"elma"` anahtarı haritada zaten
varken çalıştı ve `sonuc.second` `false` döndü — hiçbir ekleme olmadı.
`elma` değeri hâlâ `4`: harita bu çağrıdan sonra **aynen** kaldı, `99`
değeri hiçbir zaman yazılmadı. Bu, `operator[]` ile taban çizgide bir fark
yaratır: `stok["elma"] = 99` yazılsaydı değer koşulsuz üzerine yazılır ve
sınıf **değişti** olurdu. `insert`'in bu geri çevirme davranışı, bir haritayı
yalnız yoksa doldurmak isteyen kodun `operator[]` yerine `insert` ya da
`emplace` seçmesinin gerekçesidir — `emplace` aynı garantiyi taşır, farkı
elemanı bir geçici nesne kurmadan doğrudan kapsayıcının belleğinde inşa
etmesidir.

#### `erase`'in Karmaşıklığı Kapsayıcıya Göre Değişir

Bir elemanı silmek her kapsayıcıda aynı işi yapmaz. `std::vector::erase`
silinen konumdan sonraki her elemanı bir konum sola kaydırır — bu, silinen
konum başa ne kadar yakınsa o kadar çok eleman taşınacağı anlamına gelir ve
karmaşıklığı `O(n)`dir. `std::list::erase` bitişik bellek tutmadığı için
kaydırma yapmaz; yalnız silinen düğümün komşularının bağlantısını günceller
ve karmaşıklığı `O(1)`dir. `std::map::erase` ise bir anahtara göre arama
yapıp ardından ağaçtaki bir düğümü kaldırır, toplamda `O(log n)`.

```cpp
// erase-karmasikligi.cpp — vector erase kalan elemanlari kaydirir mi
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sirali{10, 20, 30, 40};

    sirali.erase(sirali.begin());

    std::printf("eleman sayisi %zu\n", sirali.size());
    std::printf("ilk eleman %d\n", sirali.front());
    return 0;
}
```

```text
eleman sayisi 3
ilk eleman 20
```

**KT6.** `sirali.erase(sirali.begin())` ilk elemanı sildi ve kalan üç eleman
bir konum sola kaydı — `front()` artık eskiden ikinci sırada duran `20`'yi
veriyor. Kaynak bu işlemden sonra **değişti**: yalnız eleman sayısı bir
azalmadı, kalan elemanların bellekteki konumu da değişti. Bir `std::list`
üzerinde aynı çağrı yapılsaydı sonuç sınıf olarak yine **değişti** olurdu
— eleman sayısı yine bir azalırdı — ama kaydırma hiç gerçekleşmezdi;
kalmayan elemanların adresleri sabit kalırdı. Bu dersin sınıflandırması
(aynen/değişti/boş/belirsiz) bir işlemin *ne kadar* iş yaptığını değil,
kaynağı *ne durumda bıraktığını* ölçtüğü için, `vector` ve `list` üzerindeki
bu iki `erase` çağrısı aynı sınıfa düşer; karmaşıklık farkı ayrı bir eksendir
ve seçim yaparken karmaşıklık ile sınıf birlikte değerlendirilir, biri
diğerinin yerine geçmez.

#### Sınır Dışı İndeksleme: Tanımsız Davranış ve Tanımlı Karşılığı

`std::vector::operator[]`, verdiği indeksin kapsayıcının sınırları içinde
olduğunu hiç denetlemez. `sirali[10]` çağrısı `sirali`'nin boyutu `3`
olduğunda **tanımsız davranıştır**: çalışma zamanı bir çökme üretebilir, çöp
bir değer döndürebilir ya da hiçbir hata vermeden "çalışabilir" — üçü de eşit
derecede olasıdır ve bu kurs hiçbirini göstermez, çünkü tanımsız davranışın
çıktısı standarda göre tanımlı değildir ve doğrulanamaz. `std::vector::at()`
aynı isteğe **tanımlı** bir karşılık verir: indeks sınır dışıysa
`std::out_of_range` fırlatır, sınır içindeyse `operator[]` ile aynı elemana
erişir. Fark performanstadır — `at()` her çağrıda bir sınır denetimi
yapar, `operator[]` yapmaz — ama bu fark yalnız sınır içindeki erişimlerde
görünür bir maliyettir; sınır dışı bir erişimde `operator[]`'in "maliyetsiz"
olması, kaynağın ne durumda kaldığının artık tanımsız olması pahasınadır.
Bir indeksin geçerliliğinden emin olunamayan her yerde `at()` tercih edilir;
indeksin geçerliliği çağıran tarafından zaten garanti edildiği sıkı döngülerde
`operator[]`'in denetimsiz hızı kabul edilir.

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

`const std::map<std::string, int>& stok` üzerinde `stok["armut"]` yazmak
**derlenmez**: `operator[]`'in `const` olmayan bir aşırı yüklemesi vardır
ve `const` bir nesne üzerinde çağrılamaz, çünkü işlecin gövdesi anahtar
yoksa eleman ekleyebilir — bu, `const` sözleşmesiyle çelişir. Aşağıdaki
blok bu satırı **yorum olarak** taşır ve onun yerine derlenen sürümü,
`const` bir haritanın yalnız `find`, `at` ve `contains` ile okunabildiğini
gösterir:

```cpp
// const-harita.cpp — const haritada operator[] neden kullanilamaz
#include <cstdio>
#include <map>
#include <string>

int main() {
    const std::map<std::string, int> stok{{"elma", 4}};

    // Asagidaki satir derlenmez, cunku operator[] const olmayan bir islev:
    // int adet = stok["armut"];

    auto it = stok.find("elma");
    std::printf("elma bulundu mu %s\n", (it != stok.end()) ? "evet" : "hayir");
    std::printf("eleman sayisi %zu\n", stok.size());
    return 0;
}
```

```text
elma bulundu mu evet
eleman sayisi 1
```

Derleyicinin bu satırı reddetmesi bir kısıtlama değil, bir güvencedir:
`const` bir haritanın yalnız sorgulanabileceğini, hiçbir okumanın onu
değiştiremeyeceğini derleme anında zorunlu kılar. Bu dersin ölçtüğü işlem
— değiştirilebilir bir haritada `operator[]` ile okuma — bu güvenceyi
taşımaz; kaynağa yazan yol açıktır ve derleyici onu kapatmaz.

#### Özet

- `std::map`'te `operator[]` ile okuma, anahtar yoksa haritaya sıfırla
  kurulmuş bir eleman ekler; bu dersin ölçtüğü işlemin sınıfı **değişti**.
- `find` ve `contains`, anahtarı ararken haritaya hiç yazmaz; kaynak
  **aynen** kalır.
- Sıralı (`vector`, `deque`, `list`, `array`), ilişkisel (`map`, `set`) ve
  sırasız (`unordered_map`, `unordered_set`) kapsayıcılar farklı karmaşıklık
  garantileri taşır (`erase` örneğin `vector`'de `O(n)`, `list`'te `O(1)`,
  `map`'te `O(log n)`); ama bu fark sınıflandırmayı etkilemez, hepsi kaynağı
  **değişti** durumuna taşır.
- `insert` ve `emplace`, anahtar zaten varsa hiçbir şey yazmaz — kaynak
  **aynen** kalır; `operator[]` ile yazmak ise koşulsuz üzerine yazar ve
  sınıf **değişti** olur.
- `at()`, anahtar yoksa istisna fırlatır ve haritayı hiç değiştirmez, bu
  yüzden `const` bir kapsayıcı üzerinde de kullanılabilir; buna karşın
  `const` bir haritada `operator[]` çağrısı derlenmez — derleyici, kaynağı
  değiştirebilecek bir yolu `const` sözleşmesiyle baştan kapatır.
- `vector::operator[]` sınır dışı bir indekste tanımsız davranışa girer;
  `vector::at()` aynı isteğe tanımlı bir istisna fırlatarak karşılık verir —
  ikisi de kaynağı okumaktan öteye gitmez, fark yalnız sınır dışı isteğe
  verilen yanıtın tanımlı olup olmadığındadır.

#### Sonraki Adım

Bu ders bir kapsayıcının bütününe ne olduğunu ölçtü. Sonraki ders sınırı bir
kapsayıcının **içindeki bir konuma** indiriyor: bir yineleyici, işaret ettiği
elemanı silme işleminden sonra hâlâ okunabilir mi, yoksa o konuma dokunmak
tanımsız davranışa mı giriyor?

### Yineleyiciler ve Geçerlilik

Source: https://academia.sh/tr/kurslar/cpp-standart-kutuphane/yineleyiciler

erase sonrası bir yineleyicinin okunmasının tanımsız davranışa girdiğinin ve geçerli kalan yineleyicilerin aynen kaldığının ölçümü.

Önceki ders `stok["armut"]` çağrısının haritaya sıfırla kurulmuş bir eleman
eklediğini, bunun kaynağı **değiştirdiğini** ölçtü. Orada değişen şey
kapsayıcının bütünüydü: eleman sayısı ve haritanın içeriği. Bu ders sınırı
daha küçük bir birime indiriyor: kapsayıcının içindeki **tek bir konuma**
işaret eden bir yineleyici. Bir eleman silindiğinde, o elemana işaret eden
yineleyici elde tutulmaya devam edilirse ne olur? Bu sorunun cevabı
kapsayıcıdan kapsayıcıya değişir — aynı `erase` çağrısı bir `vector`'da
bütün sonraki yineleyicileri etkilerken bir `list`'te yalnız silinen
elemanın kendisini etkiler — bu yüzden "yineleyici hâlâ geçerli mi" sorusu
yalnız işlemin adına bakılarak değil, hangi kapsayıcı üzerinde çağrıldığına
bakılarak yanıtlanabilir.

#### `erase` Sonrası Bir Yineleyiciyi Okumak

`std::vector::erase`, verilen konumdaki elemanı kaldırır ve ondan sonraki
elemanları bir konum kaydırır. Kaldırılan elemana ait yineleyici bu işlemden
sonra **geçersizdir** — artık var olmayan bir belleğe işaret eder ya da
üzerine başka bir elemanın taşındığı bir konuma işaret eder. Bu yineleyiciyi
okumak tanımsız davranıştır ve bu ders onu **hiç çalıştırmaz**; yalnız
`erase`'in döndürdüğü yeni, geçerli yineleyiciyi kullanır.

```cpp
// erase-donen-yineleyici.cpp — erase hangi yineleyiciyi geçerli birakiyor
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{10, 20, 30, 40};
    auto it = sayilar.begin() + 1; // 20'yi gosteriyor

    auto sonraki = sayilar.erase(it);

    std::printf("erase sonrasi eleman sayisi %zu\n", sayilar.size());
    std::printf("erase'in dondurdugu konumdaki deger %d\n", *sonraki);
    return 0;
}
```

```text
erase sonrasi eleman sayisi 3
erase'in dondurdugu konumdaki deger 30
```

**KT7.** `erase(it)` çağrısından sonra `it` değişkeni artık geçerli değil —
bu ders `it`'i erase'den sonra bir daha **hiç okumuyor**, çünkü okumak
tanımsız davranışa girer ve tanımsız davranış bu kursta hiç çalıştırılmaz.
Onun yerine `erase`'in döndürdüğü `sonraki` yineleyicisi kullanılıyor:
standart, `erase`'in kaldırılan elemandan sonraki konuma işaret eden yeni ve
geçerli bir yineleyici döndüreceğini garanti eder. Bu işlemin sınıfı
**belirsiz**: `it`'in kendisinin işlemden sonraki durumu standart tarafından
tanımlanmamıştır, tek güvence onu okumamak gerektiğidir.

#### Geçersizleştirmeyen İşlemler: Aynen Kalan Yineleyiciler

Her değiştirici işlem yineleyicileri geçersizleştirmez. `std::list::erase`,
yalnız silinen elemana ait yineleyiciyi geçersizleştirir — diğer tüm
yineleyiciler geçerliliğini korur, çünkü bağlı bir liste elemanları
bitişik bellekte tutmaz ve bir düğümün silinmesi diğer düğümleri taşımaz.

```cpp
// list-erase-diger-yineleyiciler.cpp — silinmeyen elemanin yineleyicisi ne oluyor
#include <cstdio>
#include <list>

int main() {
    std::list<int> degerler{10, 20, 30};
    auto orta = std::next(degerler.begin());       // 20
    auto son = std::next(degerler.begin(), 2);      // 30

    degerler.erase(orta);

    std::printf("silinmeyen yineleyicinin degeri %d\n", *son);
    std::printf("kalan eleman sayisi %zu\n", degerler.size());
    return 0;
}
```

```text
silinmeyen yineleyicinin degeri 30
kalan eleman sayisi 2
```

**KT8.** `son` yineleyicisi, `orta` silinirken hiç dokunulmayan bir düğüme
işaret ediyordu ve `erase(orta)` çağrısından sonra da aynı düğüme işaret
etmeye devam ediyor — değeri hâlâ `30`. Bu yineleyicinin durumu **aynen**:
silinen eleman kendisi olmadığı için standart onun geçerliliğini garanti
eder. `std::vector` için bu garanti yoktur — bir `vector`'de bir eleman
silindiğinde ondan sonraki tüm elemanlar bir konum kaydığından, silinen
konumdan sonraki her yineleyici de geçersizleşir. Bu fark, hangi
kapsayıcının seçildiğinin yineleyici kullanımını doğrudan etkilediğini
gösterir.

#### Yineleyici Kategorileri

Standart kütüphane yineleyicileri işlevsel kapasitelerine göre kategorilere
ayırır. **Girdi yineleyicisi** yalnız ileri doğru, tek geçişlik okuma
yapabilir. **İleri yineleyici** aynı yineleyiciyle birden çok geçiş yapmayı
destekler. **Çift yönlü yineleyici** (`std::list`'in yineleyicisi gibi) hem
ileri hem geri gidebilir ama sabit zamanlı atlama yapamaz. **Rastgele erişim
yineleyicisi** (`std::vector`'ın yineleyicisi gibi) `it + n` gibi sabit
zamanlı atlamayı destekler.

```cpp
// yineleyici-kategorisi.cpp — rastgele erisim ve cift yonlu farki
#include <cstdio>
#include <iterator>
#include <list>
#include <vector>

int main() {
    std::vector<int> v{1, 2, 3, 4, 5};
    std::list<int> l{1, 2, 3, 4, 5};

    auto vit = v.begin() + 3; // sabit zamanli atlama
    auto lit = std::next(l.begin(), 3); // dogrusal zamanda ilerler

    std::printf("vector uzerinden atlanan deger %d\n", *vit);
    std::printf("list uzerinden ilerlenen deger %d\n", *lit);
    return 0;
}
```

```text
vector uzerinden atlanan deger 4
list uzerinden ilerlenen deger 4
```

**KT9.** İki yineleyici de aynı sonuca ulaştı ama farklı karmaşıklıkla:
`v.begin() + 3` `vector`'ın rastgele erişim yineleyicisi olduğu için sabit
zamanda hesaplanır; `std::next(l.begin(), 3)` ise `list`'in çift yönlü
yineleyicisi üzerinde üç kez `++` çağrısına eşdeğerdir ve doğrusal zamanda
ilerler. Bu ders hız farkını ölçmüyor — süre bu kursta hiç ölçülmüyor —
yalnız iki kategorinin farklı işlemleri desteklediğini gösteriyor: `list`
yineleyicisi üzerinde `it + 3` yazmak derlenmez, çünkü çift yönlü yineleyici
`operator+` sağlamaz.

#### Aralık Tabanlı `for` ve Örtük Yineleyiciler

Aralık tabanlı `for` döngüsü, arka planda `begin()` ve `end()` çağırarak
yineleyicileri örtük kullanır. Döngü gövdesinde kapsayıcıya eleman
eklenirse ya da elemanlar silinirse, örtük yineleyiciler de aynı geçerlilik
kurallarına tabidir — bu yüzden bir `vector` üzerinde aralık tabanlı `for`
gövdesinde `push_back` çağırmak, döngünün geri kalanını tanımsız davranışa
sokabilir. Bu ders böyle bir değişikliği hiç yapmıyor.

```cpp
// araligbdan-for-salt-okuma.cpp — araligb tabanlı for elemanlari nasil gezer
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{5, 10, 15};
    int toplam = 0;

    for (int deger : sayilar) {
        toplam += deger;
    }

    std::printf("toplam %d\n", toplam);
    std::printf("eleman sayisi degisti mi %s\n",
        (sayilar.size() == 3) ? "hayir" : "evet");
    return 0;
}
```

```text
toplam 30
eleman sayisi degisti mi hayir
```

**KT10.** Döngü yalnız her elemanı okudu, kapsayıcıya hiç yazmadı; `sayilar`
işlem sonunda **aynen** kaldı. Örtük `begin()`/`end()` çağrıları da bu
yüzden döngü boyunca geçerli kaldı — kapsayıcının boyutu ya da içeriği
değişmediği için yineleyicilerin geçersizleşeceği bir durum hiç oluşmadı.

#### Boş ile Belirsiz Arasındaki Fark

`end()` yineleyicisi ile geçersizleşmiş bir yineleyici bazen aynı şeymiş
gibi karıştırılıyor; ikisi de "okunmaması gereken" bir konumu gösteriyor
gibi görünüyor. Ama standart bu ikisine tamamen farklı garantiler bağlıyor.
`end()`, kapsayıcının son elemanından bir sonraki konumu gösteren, her
zaman tanımlı ve karşılaştırmaya açık bir değerdir — `it == kapsayici.end()`
yazmak her koşulda geçerlidir, çünkü `end()`'in kendisi hiçbir zaman
geçersiz değildir; yalnız `*end()` ile onu **dereferanslamak** tanımsızdır.
Bir kapsayıcı **boş** olduğunda `begin() == end()` olur — bu, "aranan eleman
yok" demenin tanımlı bir yoludur, `find`'ın bulamama durumunda döndürdüğü
değer de budur. Geçersizleşmiş bir yineleyici ise **belirsiz**dir: değeri
karşılaştırmaya bile açık değildir, `it == baska_it` yazmak da tanımsız
davranışa girebilir, çünkü standart geçersizleşmiş bir yineleyicinin hangi
işlemleri desteklediğini hiç garanti etmez. Bu fark önemlidir çünkü bir
kodun "boş" durumunu denetlemesi (`it == end()`) her zaman güvenlidir, ama
"acaba bu yineleyici hâlâ geçerli mi" sorusunu yineleyicinin kendisine
sorarak yanıtlamak mümkün değildir — geçerlilik yineleyicide taşınan bir
bilgi değildir, kaynağın geçirdiği işlemlerden çıkarılması gereken bir
bilgidir.

#### `push_back` ve Yeniden Ayırma: Bütün Yineleyicileri Etkileyen İşlem

`erase` yalnız silinen konumdan sonrasını etkiler; `std::vector::push_back`
ise çok daha geniş bir hasar bırakabilir. `vector`'ın ayrılmış kapasitesi
dolmuşsa `push_back` yeni, daha büyük bir bellek bloğu ayırır, tüm mevcut
elemanları oraya taşır ve eski bloğu serbest bırakır — bu durumda eklemeden
önce alınmış **her** yineleyici, işaretçi ve referans geçersizleşir, yalnız
eklenen elemana ait olanlar değil. Kapasite yeterliyse taşıma hiç olmaz ve
mevcut yineleyiciler aynen kalır; ama çağıran taraf bunu `size()` ve
`capacity()`'yi karşılaştırmadan önceden bilemez.

```cpp
// push-back-yeniden-ayirma.cpp — kapasite dolunca push_back yineleyicileri nasil etkiler
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar;
    sayilar.reserve(2);
    sayilar.push_back(1);
    sayilar.push_back(2);

    bool kapasite_doldu = (sayilar.size() == sayilar.capacity());
    sayilar.push_back(3);

    std::printf("ekleme oncesi kapasite dolmus muydu %s\n",
        kapasite_doldu ? "evet" : "hayir");
    std::printf("eleman sayisi %zu\n", sayilar.size());
    std::printf("ilk eleman %d\n", sayilar.front());
    return 0;
}
```

```text
ekleme oncesi kapasite dolmus muydu evet
eleman sayisi 3
ilk eleman 1
```

**KT11.** `reserve(2)` ile ayrılan kapasite iki eleman eklendiğinde tam
doldu; üçüncü `push_back` çağrısı bu yüzden `vector`'ı yeni bir bloğa
taşıdı. Taşıma tamamlandıktan sonra kapasiteden önce alınmış olabilecek
her yineleyici **belirsiz** duruma girerdi — bu ders böyle bir yineleyiciyi
saklayıp taşımadan sonra okumuyor, yalnız `front()` ile kapsayıcının
kendisinden yeniden okuyor. `front()`'un `1` döndürmesi, elemanların
değerinin taşımadan etkilenmediğini gösteriyor; etkilenen şey elemanların
değeri değil, onlara önceden alınmış yineleyicilerin geçerliliği.

#### `std::map`'te Yineleyici Kararlılığı

İlişkisel kapsayıcılar bu konuda `vector`'dan çok `list`'e benzer.
`std::map` bir dengeli ikili arama ağacıyla gerçeklenir ve elemanlar
bitişik bellekte durmaz; bir düğüm eklendiğinde ya da silindiğinde diğer
düğümler yer değiştirmez, yalnız ağacın bağlantıları güncellenir. Bu yüzden
`std::map::erase` de tıpkı `std::list::erase` gibi yalnız silinen elemana
ait yineleyiciyi geçersizleştirir; kalan her yineleyici, işaretçi ve
referans aynen geçerli kalır. `std::map::insert` da benzer bir garanti
taşır: yeni bir düğüm eklemek var olan düğümleri hiç taşımaz, dolayısıyla
eklemeden önce alınmış hiçbir yineleyici geçersizleşmez.

```cpp
// map-erase-diger-yineleyiciler.cpp — map erase diger yineleyicileri etkiler mi
#include <cstdio>
#include <map>

int main() {
    std::map<int, int> tablo{{1, 10}, {2, 20}, {3, 30}};
    auto uc = tablo.find(3);

    tablo.erase(tablo.find(1));

    std::printf("silinmeyen anahtarin degeri %d\n", uc->second);
    std::printf("kalan eleman sayisi %zu\n", tablo.size());
    return 0;
}
```

```text
silinmeyen anahtarin degeri 30
kalan eleman sayisi 2
```

**KT12.** `tablo.find(1)`'e ait yineleyici silindi, ama `uc` başka bir
anahtara işaret ediyordu ve `erase` çağrısından etkilenmedi — `uc->second`
hâlâ `30`. Bu durumun sınıfı `std::list`'teki karşılığıyla aynı: **aynen**.
`std::vector`'da aynı senaryo kurulsaydı — üç elemandan ilkini silmek —
kalan elemanlar kaydığı için ikinci ve üçüncü elemana ait yineleyiciler
geçersizleşirdi; burada geçersizleşmiyor olması `map`'in bellek düzeninin
`vector`'dan temelden farklı olmasından kaynaklanıyor, silme işleminin
"daha güvenli" yazılmış olmasından değil.

#### Geçersiz Yineleyici Neden Okunmaz

`erase`'in döndürdüğü yineleyiciyi kullanmak yerine, silinen elemana ait
eski yineleyiciyi doğrudan okumak isteyen bir kod satırı derlenir ama
**çalıştırılamaz** — bu, derleyicinin yakalayamayacağı bir tanımsız
davranıştır. Aşağıdaki blok o satırı yalnız yorum olarak taşır ve neden hiç
çalıştırılmadığını açıklar:

```cpp
// gecersiz-yineleyici-yorumu.cpp — gecersiz yineleyiciyi okumamanin gerekcesi
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{10, 20, 30, 40};
    auto it = sayilar.begin() + 1;

    sayilar.erase(it);

    // Asagidaki satir tanimsiz davranistir ve bu yuzden hic calistirilmaz:
    // std::printf("%d\n", *it);

    std::printf("erase sonrasi eleman sayisi %zu\n", sayilar.size());
    return 0;
}
```

```text
erase sonrasi eleman sayisi 3
```

Derleyici `*it` satırını reddetmez, çünkü `it`'in geçerliliği bir tip
denetimi değil, bir çalışma zamanı sözleşmesidir. Standart bu sözleşmeyi
ihlal eden kodun davranışını tanımlamadığı için, güvenli olan tek yol o
satırı hiç yazmamak ya da hiç çalıştırmamaktır — bu kursun her dersinde
uygulanan kural budur. Bu, statik analiz araçlarının bazılarının neden bu
sınıf hataları tam yakalayamadığını da açıklar: `it`'in geçerliliği
`it`'in tipinde durmaz, `it`'i üreten kapsayıcının geçirdiği işlem
geçmişinde durur — bir araç bu geçmişi izlemedikçe, geçerli görünen bir
yineleyici çağrısının aslında geçersizleşmiş bir konuma eriştiğini
göremez.

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

Aynı `erase` çağrısı, hedef aldığı yineleyiciyle sınırlıdır — kalan
yineleyicilere ne olduğu **kapsayıcının türüne**, işlemin kendisine değil,
bağlıdır. Dört elemanlı bir `std::list` üzerinde ortadaki iki elemanı
silip uçlardaki yineleyicileri yeniden okuyalım:

```cpp title="ucta-kalan-yineleyiciler.cpp"
#include <cstdio>
#include <iterator>
#include <list>

int main() {
    std::list<int> sayilar{1, 2, 3, 4};

    auto ilk = sayilar.begin();
    auto son = std::prev(sayilar.end());
    auto orta_bir = std::next(sayilar.begin());
    auto orta_iki = std::next(sayilar.begin(), 2);

    sayilar.erase(orta_bir);
    sayilar.erase(orta_iki);

    std::printf("%d %d\n", *ilk, *son);
    return 0;
}
```

```text title="çıktı"
1 4
```

`erase(orta_bir)` yalnız `orta_bir`'in gösterdiği düğümü geçersizleştirir;
`ilk` ve `son` başka düğümlere bağlıdır ve etkilenmez. İkinci `erase`
çağrısı da aynı kuralı izler: `orta_iki` kendi düğümüyle birlikte geçersiz
olur, ama bu, zaten silinmiş `orta_bir`'e bir şey katmaz — `ilk` ve `son`
işlemden önceki değerlerini **aynen** göstermeye devam eder. Aynı senaryoyu
bir `std::vector` ile kursaydık sonuç başka olurdu: eleman silindiğinde
ardından gelen her eleman bir konum kaydırılır, dolayısıyla `son` gibi
sondan alınmış bir yineleyici de geçersizleşirdi. Ölçümün sınıfını
belirleyen şey silinen eleman sayısı ya da yineleyicinin nereden alındığı
değil, kapsayıcının bellek düzenidir: düğüm tabanlı bir yapıda yalnız
dokunulan düğüm geçersizleşirken, bitişik bellekte bir silme kendisinden
sonraki her konumu etkiler.

#### Özet

- `std::vector::erase`, silinen elemana ait yineleyiciyi geçersizleştirir;
  onu erase'den sonra okumak **belirsiz** davranıştır ve hiç çalıştırılmaz.
  Çağrının döndürdüğü yeni yineleyici ise geçerlidir ve kaldırılan
  elemandan sonraki konumu gösterir. Sınırlayıcı ölçüm: aynı işlem bir
  `std::list`'te yalnız hedef aldığı düğümü geçersizleştirir; dokunulmayan
  yineleyiciler **aynen** kalır — geçerliliği belirleyen işlem değil
  kapsayıcının bellek düzenidir.
- `std::list::erase` ve `std::map::erase`/`insert`, düğüm tabanlı
  yapılarında yalnız dokunulan düğümün yineleyicisini geçersizleştirir;
  diğer tüm yineleyiciler **aynen** kalır — bellek bitişik olmadığı için
  eleman kaydırma hiç gerçekleşmez.
- Yineleyiciler girdi, ileri, çift yönlü ve rastgele erişim kategorilerine
  ayrılır; kategori, hangi işlemlerin desteklendiğini belirler.
- Bu dersin merkezi ölçümünün sınıfı **belirsiz**: erase edilen bir elemana
  ait yineleyicinin durumu standart tarafından tanımlanmaz, tek güvence onu
  bir daha okumamaktır.
- `end()` her zaman tanımlı ve karşılaştırılabilir bir değerdir; bir
  kapsayıcının **boş** olması `begin() == end()` ile ifade edilir. Bu,
  geçersizleşmiş bir yineleyicinin **belirsiz** durumundan ayrı bir şeydir
  — biri "burada eleman yok" der, öbürü hiçbir garanti taşımaz.
- `std::vector::push_back`, kapasite dolmuşsa yalnız eklenen elemanı değil,
  önceden alınmış her yineleyiciyi geçersizleştirir; kapasite yeterliyse
  hiçbir yineleyici etkilenmez. Bu yüzden bir `vector` üzerinde döngü
  içinde `push_back` çağırırken önceden alınmış yineleyicilere güvenmemek
  gerekir.

#### Sonraki Adım

Bu ders tek bir yineleyicinin geçerliliğine odaklandı. Sonraki ders bir
kapsayıcının tüm elemanları üzerinde çalışan standart algoritmalara geçiyor:
`std::sort` bir kapsayıcıyı yerinde nasıl değiştiriyor ve bu değişiklik
hangi yineleyicileri etkiliyor?

### Algoritmalar

Source: https://academia.sh/tr/kurslar/cpp-standart-kutuphane/algoritmalar

std::sort'un bir vector'ü yerinde nasıl değiştirdiğinin ve std::count gibi salt okuma algoritmaların kaynağı aynen bıraktığının ölçümü.

Önceki ders `erase` sonrası bir yineleyicinin okunmasının **belirsiz**
olduğunu, `erase`'in döndürdüğü yeni yineleyicinin ise geçerli kaldığını
gösterdi. O ders tek bir konumu izliyordu. Bu ders sınırı yeniden
genişletiyor: standart kütüphanenin bir kapsayıcının **tüm** elemanları
üzerinde çalışan hazır algoritmalarına geçiyor. `std::sort` bir `vector`'ü
sıralarken kaynağa ne bırakıyor? Aynı soru bu dersteki her algoritma için
tekrar sorulacak; bazı algoritmalar kaynağı okuyup hiç değiştirmezken
bazıları kaynağın kendisini kalıcı olarak yeniden düzenler, ve bu fark
algoritmanın adından değil standart tanımından çıkarılır.

#### `std::sort`: Yerinde Değiştiren Bir Algoritma

`std::sort`, verilen yineleyici aralığındaki elemanları belirtilen
sıralama kuralına göre **yerinde** düzenler — yeni bir kapsayıcı döndürmez,
verilen aralığın kendisini değiştirir. Varsayılan kural `operator<`'tir.

```cpp
// sort-yerinde.cpp — std::sort bir vector'u nasil degistiriyor
#include <algorithm>
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{40, 10, 30, 20};

    std::sort(sayilar.begin(), sayilar.end());

    std::printf("siralanmis dizi:");
    for (int deger : sayilar) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    return 0;
}
```

```text
siralanmis dizi: 10 20 30 40
```

**KT13.** `std::sort` çağrısından sonra `sayilar`'ın eleman sayısı değişmedi
ama elemanların **sırası** değişti — bu işlemin sınıfı **değişti**. Standart,
sıralamadan sonra hangi tam diziliş olacağını garanti eder (küçükten büyüğe,
`operator<` kuralına göre), yani sonuç belirsiz değil, tam tersine
öngörülebilir ve tekrarlanabilir. Bu, `sort`'u önceki dersteki `erase`'den
ayıran şey: `erase`'in bıraktığı yineleyici belirsizdi, ama `sort`'un
bıraktığı dizi tanımlıdır.

#### Salt Okuma Algoritmalar: Kaynağı Aynen Bırakanlar

Her algoritma kaynağı değiştirmez. `std::count`, verilen değere eşit eleman
sayısını döndürür; `std::find_if`, bir yüklemi sağlayan ilk elemana bir
yineleyici döndürür; `std::accumulate`, elemanları biriktirerek toplar.
Üçü de aralığı yalnız okur.

```cpp
// salt-okuma-algoritmalar.cpp — count, find_if, accumulate kaynagi degistiriyor mu
#include <algorithm>
#include <cstdio>
#include <numeric>
#include <vector>

int main() {
    std::vector<int> sayilar{4, 7, 2, 7, 9};
    auto once = sayilar;

    long adet = std::count(sayilar.begin(), sayilar.end(), 7);
    auto it = std::find_if(sayilar.begin(), sayilar.end(),
        [](int deger) { return deger > 8; });
    int toplam = std::accumulate(sayilar.begin(), sayilar.end(), 0);

    std::printf("7 adedi %ld\n", adet);
    std::printf("8'den buyuk ilk deger %d\n", *it);
    std::printf("toplam %d\n", toplam);
    std::printf("kaynak degisti mi %s\n", (sayilar != once) ? "evet" : "hayir");
    return 0;
}
```

```text
7 adedi 2
8'den buyuk ilk deger 9
toplam 29
kaynak degisti mi hayir
```

**KT14.** Üç algoritma da `sayilar`'ı yalnız okudu; işlem sonunda kaynak
işlem öncesindeki kopyasıyla eşit — sınıf **aynen**. Bu, `std::sort`'la
doğrudan karşıtlık kurar: ikisi de aynı `vector` üzerinde çalışır, ama
`sort` diziliş sırasını değiştirirken bu üç algoritma diziliş sırasına hiç
dokunmaz. Bir algoritmanın adı genelde niyetini gösterir — `count`, `find`,
`accumulate` fiilleri "sorgula" der, `sort`, `reverse`, `transform` fiilleri
"değiştir" der — ama bu bir garanti değil, bir alışkanlıktır; kesin cevap
her zaman ilgili algoritmanın standart tanımındadır.

#### Bu Kurs Saymaz: Ölçümün Ekseni Çalışma Sayısı Değildir

`Trait'ler, Genellikler ve Hata Yönetimi` (Rust, M10/K03) bir gövdenin
**kaç kez koştuğunu** ölçerek soyutlamanın çalışma zamanı bedelini görünür
kılıyordu — orada ölçülen şey bir çağrının kaç kez tekrarlandığıydı. Bu
kurs farklı bir eksende çalışıyor ve **saymaz**: `std::count`'un
döndürdüğü `2` gibi bir sayı, aranan değere kaç elemanın eşit olduğunu
söyleyen bir **sonuçtur**, algoritmanın kaç kez çağrıldığını ya da bir
gövdenin kaç kez çalıştığını sayan bir ölçüm değildir. Bu dersin sorduğu
soru hep aynı: bir işlem kaynağı hangi sınıfa (aynen/değişti/boş/belirsiz)
bıraktı? `std::count`'un kendisi bir kez çağrılır ve kaynağı **aynen**
bırakır; içeride kaç karşılaştırma yaptığı bu ölçümün konusu değildir.
Bir okurun iki kursu karıştırmaması için bu ayrım önemlidir: soyutlamanın
çalışma zamanı bedelini öğrenmek isteyen `rust-soyutlama`'ya, bir işlemin
kaynağı ne durumda bıraktığını öğrenmek isteyen bu kursa bakar.

#### Özel Karşılaştırma Yüklemi

`std::sort`'un üçüncü bir argümanı olabilir: iki elemanı karşılaştıran bir
yüklem. Bu yüklem `operator<`'in yerine geçer ve sıralama kuralını
programcı tanımlar.

```cpp
// sort-yuklem.cpp — ozel karsilastirma ile buyukten kucuge siralama
#include <algorithm>
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{40, 10, 30, 20};

    std::sort(sayilar.begin(), sayilar.end(),
        [](int a, int b) { return a > b; });

    std::printf("buyukten kucuge:");
    for (int deger : sayilar) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    return 0;
}
```

```text
buyukten kucuge: 40 30 20 10
```

**KT15.** Yüklem `a > b` döndürdüğünde `a`'nın `b`'den önce geleceğini
belirtir; bu yüzden dizi büyükten küçüğe sıralandı. Bu işlemin sınıfı da
**değişti** — önceki `sort` çağrısıyla aynı sınıfta, çünkü ikisi de aynı
algoritmanın farklı bir kuralla çağrılmış hâli. Yüklemin kendisi `sayilar`'ı
hiç okumadı ya da değiştirmedi; yalnız iki `int` değeri karşılaştırdı. Bu
ayrım önemli: `sort`'u değiştirici yapan yineleyici aralığına yazması,
yüklemi değiştirici yapmayan ise yalnız değer karşılaştırması olmasıdır.

#### Algoritmalar Yineleyici Çiftiyle Çalışır, Kapsayıcıyla Değil

Standart algoritmaların hiçbiri bir kapsayıcı tipi almaz; hepsi bir başlangıç
ve bitiş yineleyicisi alır. Bu, aynı algoritmanın bir `vector`'ün tamamında,
bir `vector`'ün bir alt aralığında ya da bir C dizisinde çalışabilmesini
sağlar.

```cpp
// yineleyici-cifti.cpp — ayni algoritma farkli araliklarda
#include <algorithm>
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{5, 3, 8, 1, 9, 2};

    std::sort(sayilar.begin(), sayilar.begin() + 3);

    std::printf("kismi siralanmis dizi:");
    for (int deger : sayilar) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    return 0;
}
```

```text
kismi siralanmis dizi: 3 5 8 1 9 2
```

**KT16.** `std::sort(sayilar.begin(), sayilar.begin() + 3)` yalnız ilk üç
elemanı sıraladı; kalan üç eleman (`1 9 2`) girdi sırasında kaldı. Bu, aynı
`std::sort` çağrısının farklı bir aralıkla farklı bir sonuç ürettiğini
gösteriyor — algoritma kapsayıcının tamamını değil, kendisine verilen
yineleyici çiftini görüyor. Bu işlemin sınıfı yine **değişti**, çünkü
`sayilar`'ın bir kısmı (ilk üç eleman) diziliş olarak değişti; ancak
değişikliğin kapsamı bu kez kapsayıcının bütünü değil, verilen aralıkla
sınırlı. Bu tasarımın bir sonucu, algoritmaların kapsayıcı sınırlarını hiç
bilmemesidir: `std::sort`'a verilen `sayilar.begin() + 3` yineleyicisi
kapsayıcının kendi sonunu değil, çağıranın belirttiği sınırı gösteriyor.
Aynı algoritma bir ham C dizisi üzerinde de, işaretçi çiftiyle çağrılarak
aynı şekilde çalışır — kapsayıcı kavramı algoritmanın arayüzünde hiç yer
almaz, yalnız yineleyici kategorisi (burada rastgele erişim) arayüzün
gerektirdiği şeydir.

#### `std::transform`: Yeni Bir Hedefe Yazan Algoritma

`std::sort` kaynak aralığın kendisini değiştiriyordu. `std::transform` farklı
bir kalıp sunar: kaynağı okur, her elemana bir işlev uygular ve sonucu
**ayrı** bir hedefe yazar. Kaynak, hedeften ayrı bir kapsayıcı olduğu sürece
`std::transform` kaynağa hiç dokunmaz.

```cpp
// transform-ayri-hedef.cpp — std::transform kaynagi mi hedefi mi degistiriyor
#include <algorithm>
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> kaynak{1, 2, 3, 4};
    std::vector<int> hedef(kaynak.size());

    std::transform(kaynak.begin(), kaynak.end(), hedef.begin(),
        [](int deger) { return deger * deger; });

    std::printf("kaynak:");
    for (int deger : kaynak) {
        std::printf(" %d", deger);
    }
    std::printf("\n");

    std::printf("hedef:");
    for (int deger : hedef) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    return 0;
}
```

```text
kaynak: 1 2 3 4
hedef: 1 4 9 16
```

**KT17.** `kaynak` işlem sonunda hâlâ `1, 2, 3, 4` — `std::transform`
kaynağı yalnız okudu, hiç yazmadı; kaynağın sınıfı **aynen**. `hedef` ise
kareler dizisiyle dolduruldu. `std::transform`'un üçüncü argümanı hedefin
başlangıç yineleyicisidir; bu argüman kaynağın kendisi (`kaynak.begin()`)
olarak da verilebilir — bu durumda algoritma her elemanı okuyup aynı konuma
geri yazar ve kaynak bu kez **değişti** sınıfına girer. Hangi sınıfın
geçerli olduğu hedefin kaynakla aynı aralık olup olmadığına bağlıdır; bu
ders ikisini ayrı tutarak `std::transform`'un varsayılan davranışını, yani
kaynağı okuyup ayrı bir yere yazmayı gösteriyor. `std::transform`'un hedef
argümanı bir yineleyici olduğu için hedefin önceden yeterli boyutta
ayrılmış olması çağıranın sorumluluğundadır — algoritma hedefe eleman
eklemez, yalnız var olan konumlara yazar; hedef `kaynak` kadar büyük
değilse yazma, ayrılmış bellek sınırının dışına taşar ve bu da tanımsız
davranıştır. Bu ders `hedef`'i her zaman `kaynak.size()` ile boyutlandırarak
bu riski hiç oluşturmuyor.

#### `std::stable_sort`: Eşit Elemanların Sırasını Koruyan Sıralama

`std::sort`, eşit kabul edilen iki eleman arasındaki göreli sırayı korumayı
**garanti etmez** — bu, algoritmanın izin verilen bir yan etkisidir, bir
hata değildir. `std::stable_sort` aynı sıralama işini yapar ama ek bir
garanti taşır: karşılaştırma yüklemine göre eşit sayılan elemanlar, girdi
sırasındaki göreli konumlarını korur. Bu garanti bedelsiz değildir —
`std::sort` ortalama `O(n log n)` karmaşıklıkla çalışırken `std::stable_sort`
yeterli ek bellek yoksa `O(n log² n)`'e düşebilir; ek bellek varsa yine
`O(n log n)` sağlar.

```cpp
// stable-sort-esitlik.cpp — esit elemanlarin sirasi std::sort ile stable_sort arasinda nasil farkli
#include <algorithm>
#include <cstdio>
#include <utility>
#include <vector>

int main() {
    std::vector<std::pair<int, char>> kayitlar{
        {2, 'a'}, {1, 'b'}, {2, 'c'}, {1, 'd'}};

    std::stable_sort(kayitlar.begin(), kayitlar.end(),
        [](const auto& sol, const auto& sag) { return sol.first < sag.first; });

    std::printf("siralanmis kayitlar:");
    for (const auto& kayit : kayitlar) {
        std::printf(" (%d,%c)", kayit.first, kayit.second);
    }
    std::printf("\n");
    return 0;
}
```

```text
siralanmis kayitlar: (1,b) (1,d) (2,a) (2,c)
```

**KT18.** Anahtara göre eşit sayılan `{1, 'b'}` ve `{1, 'd'}` çifti,
sıralamadan sonra da girdideki göreli sırasını korudu — `b` hâlâ `d`'den
önce geliyor. Aynı şey `{2, 'a'}` ve `{2, 'c'}` için de geçerli. Bu
işlemin sınıfı da **değişti** — dizi baştan sona yeniden düzenlendi — ama
`std::sort`'tan farkı, eşitler arasındaki göreli sıranın da korunmuş
olduğu ek bir garantidir. `std::sort` aynı girdide `(1,d)` ile `(1,b)`'yi
ters sırada bırakabilirdi; bu, `std::sort`'un ihlal ettiği bir kural
değil, hiç vermediği bir sözdür.

#### `std::list` Neden Kendi `sort`'unu Taşır

`std::sort`, rastgele erişim yineleyicisi gerektirir — ortalama `O(n log n)`
karmaşıklığını sağlayabilmek için elemanlar arasında sabit zamanlı atlama
yapması gerekir. Bu yüzden `std::sort`, `std::list`'in çift yönlü
yineleyicisiyle **derlenmez**. Standart kütüphane bu durum için ayrı bir
üye işlev sağlar: `std::list::sort`.

```cpp
// list-sort-uye-islevi.cpp — list icin ayri bir sort neden gerekli
#include <cstdio>
#include <list>

int main() {
    std::list<int> degerler{40, 10, 30, 20};

    // Asagidaki satir derlenmez, cunku std::sort rastgele erisim yineleyicisi ister:
    // std::sort(degerler.begin(), degerler.end());

    degerler.sort();

    std::printf("siralanmis liste:");
    for (int deger : degerler) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    return 0;
}
```

```text
siralanmis liste: 10 20 30 40
```

`std::list::sort`'un ayrı bir üye işlev olarak var olmasının nedeni bu
kısıtlama: `list`'in düğümleri bitişik bellekte durmadığı için genel
`std::sort` algoritması onlar üzerinde çalışamaz, ama `list` kendi
düğümlerinin bağlantılarını yeniden kurarak aynı sonucu farklı bir
karmaşıklıkla üretebilir. Bu, "her kapsayıcı her algoritmayla çalışmaz"
kuralının somut bir örneği. Aynı kısıtlama diğer rastgele-erişim-isteyen
algoritmalar için de geçerlidir: `std::nth_element` ve ikili aramaya
dayanan `std::binary_search`, `std::lower_bound` gibi algoritmalar da
`std::list` üzerinde doğrudan çağrılamaz; standart kütüphane bu boşluğu
her kapsayıcı için ayrı bir üye işlevle doldurmaz, yalnız `list::sort`,
`list::merge`, `list::unique` ve `list::remove` gibi düğüm bağlantısı
gerektiren birkaç işlemi üye olarak sunar. Kapsayıcı seçimi bu yüzden
yalnız karmaşıklık garantilerini değil, hangi standart algoritmaların
doğrudan kullanılabileceğini de belirler.

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

Aynı veri, aynı anahtara göre karşılaştırma yüklemiyle iki farklı algoritmaya
verildiğinde kaynağın ne kadarı güvenilir biçimde okunabilir sorusu
algoritmanın **adına** bağlıdır. `std::sort`, eşit anahtarlı elemanlar
arasındaki göreli sırayı korumayı garanti etmez — bu yüzden o sırayı
yazdırmak, standardın vermediği bir sözü varmış gibi göstermek olur. Aynı
veriyi bir de `std::stable_sort` ile sıralayıp iki sonucu karşılaştıralım:

```cpp title="isim-siraya-ne-soyler.cpp"
#include <algorithm>
#include <cstdio>
#include <utility>
#include <vector>

int main() {
    std::vector<std::pair<int, char>> anahtarli{
        {2, 'a'}, {1, 'b'}, {2, 'c'}, {1, 'd'}};
    std::vector<std::pair<int, char>> kararli = anahtarli;

    std::sort(anahtarli.begin(), anahtarli.end(),
        [](const auto& sol, const auto& sag) { return sol.first < sag.first; });
    std::stable_sort(kararli.begin(), kararli.end(),
        [](const auto& sol, const auto& sag) { return sol.first < sag.first; });

    std::printf("sort anahtar sirasi:");
    for (const auto& kayit : anahtarli) {
        std::printf(" %d", kayit.first);
    }
    std::printf("\n");

    std::printf("stable_sort tam sira:");
    for (const auto& kayit : kararli) {
        std::printf(" %d%c", kayit.first, kayit.second);
    }
    std::printf("\n");
    return 0;
}
```

```text title="çıktı"
sort anahtar sirasi: 1 1 2 2
stable_sort tam sira: 1b 1d 2a 2c
```

`anahtarli` üzerinde yalnız anahtarları (`1 1 2 2`) yazdırdık; harfleri
yazdırmadık, çünkü `std::sort`'un eşit anahtarlar arasında hangi harfi önce
bıraktığı standart tarafından tanımlanmayan bir ayrıntıdır — aynı derleyici
bile farklı girdi boyutlarında farklı bir iç sıralama uygulayabilir.
`kararli` üzerinde ise tam kayıtları yazdırabildik, çünkü `std::stable_sort`
eşit anahtarların girdideki göreli sırasını korumayı garanti eder: `1b`
her zaman `1d`'den önce, `2a` her zaman `2c`'den önce gelir. İki çağrı da
aynı veriyi **değişti** sınıfına taşır — kaynak yerinde yeniden düzenlenir
— ama kaynağın hangi ayrıntısının güvenle okunup bildirilebileceğini
algoritmanın adı belirler: `sort` yalnız nihai anahtar sırasını, `stable_sort`
ise anahtar sırasıyla birlikte eşitler arası göreli konumu da garanti eder.

#### Özet

- `std::sort`, verilen yineleyici aralığındaki elemanların sırasını yerinde
  değiştirir; bu dersin merkezi ölçümünün sınıfı **değişti**. Özel bir
  karşılaştırma yüklemi vermek sıralama kuralını değiştirir ama yüklemin
  kendisi kaynağı okumaz ya da değiştirmez; `std::stable_sort` ise eşit
  sayılan elemanların göreli sırasını da korur, bu ek garanti yeterli ek
  bellek yoksa `O(n log² n)`'e çıkan bir karmaşıklık bedeliyle gelir.
  Sınırlayıcı ölçüm: aynı veri `std::sort` ve `std::stable_sort`'a
  verildiğinde ikisi de kaynağı **değişti** sınıfına taşır, ama kaynağın
  hangi ayrıntısı güvenle bildirilebilir sorusu algoritmanın adına bağlıdır
  — `sort` yalnız anahtar sırasını garanti eder, `stable_sort` eşitler
  arası göreli sırayı da.
- `std::count`, `std::find_if`, `std::accumulate` gibi salt okuma
  algoritmalar kaynağı **aynen** bırakır.
- Bu kurs çalışma sayısı saymaz: `rust-soyutlama`'nın "kaç kez koştu"
  ekseni bu kursun ölçüm ekseni değildir; burada ölçülen şey kaynağın
  hangi sınıfta kaldığıdır.
- Algoritmalar bir kapsayıcı tipi değil, bir yineleyici çifti alır; aynı
  algoritma farklı aralıklarla farklı kapsamda çalışır.
- `std::transform`, kaynağı okuyup ayrı bir hedefe yazdığında kaynağın
  sınıfı **aynen** kalır; hedefi kaynağın kendisi yapmak sınıfı
  **değişti**'ye çevirir.
- `std::sort`, rastgele erişim yineleyicisi ister ve `std::list` üzerinde
  derlenmez; `list` bunun yerine kendi `sort` üye işlevini sağlar.

#### Sonraki Adım

Bu ders `std::sort`'un bir kapsayıcıyı yerinde nasıl değiştirdiğini gösterdi.
Sonraki ders bir kapsayıcıdan eleman **kaldırmanın** iki adımlı bir deyim
gerektirdiğini ele alıyor: `std::remove`, elemanları neden gerçekten
silmiyor da yalnız kuyruğa doğru taşıyor?

### Sil–Kaldır Deyimi

Source: https://academia.sh/tr/kurslar/cpp-standart-kutuphane/sil-kaldir-deyimi

std::remove sonrası vector'ün kuyruğunda kalan elemanların belirsiz değer taşıdığının ve erase ile bu kuyruğun gerçekten kaldırıldığının ölçümü.

Önceki ders `std::sort`'un bir `vector`'ün elemanlarını yerinde yeniden
sıraladığını, bunun kaynağı **değiştirdiğini** gösterdi. Sıralama sırasında
kapsayıcının eleman sayısı hiç değişmedi. Bu ders eleman sayısını
**azaltan** bir işleme geçiyor, ama beklenmedik bir ayrıntıyla: adında
"kaldır" geçen `std::remove`, bir `vector`'ün boyutunu değiştirmez. Peki ne
yapar?

Bu ayrıntı yeni başlayanların en çok şaşırdığı noktalardan biridir: bir işlev
adının vaat ettiği şeyle gerçekte yaptığı şey her zaman birebir örtüşmez.
`std::remove` bir algoritmadır ve algoritmalar yalnız yineleyici çiftleriyle
çalışır — kapsayıcının kendisine erişimleri yoktur, dolayısıyla kapsayıcının
boyutunu küçültme yetkileri de yoktur. Bu ders, bu sınırın nereden geldiğini
ve gerçek kaldırmanın nasıl tamamlandığını adım adım gösteriyor.

#### `std::remove`: Kaldırmayan Bir "Kaldırma"

`std::remove`, verilen aralıktaki elemanları gezer; aranan değere eşit
olmayanları aralığın başına doğru taşır ve yeni "geçerli" bölgenin bittiği
yeri gösteren bir yineleyici döndürür. Kapsayıcının boyutunu değiştirmez —
değiştiremez de, çünkü bir yineleyici çiftiyle çalışır, kapsayıcının
kendisiyle değil.

```cpp
// remove-siz-erase.cpp — std::remove tek basina ne yapiyor
#include <algorithm>
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{1, 7, 2, 7, 3, 7};
    std::size_t once = sayilar.size();

    auto yeni_son = std::remove(sayilar.begin(), sayilar.end(), 7);

    std::printf("boyut degisti mi %s\n",
        (sayilar.size() != once) ? "evet" : "hayir");
    std::printf("gecerli bolgenin uzunlugu %ld\n",
        static_cast<long>(std::distance(sayilar.begin(), yeni_son)));
    return 0;
}
```

```text
boyut degisti mi hayir
gecerli bolgenin uzunlugu 3
```

**KT19.** `std::remove` çağrısından sonra `sayilar.size()` hâlâ `6` —
kapsayıcının boyutu değişmedi. `yeni_son` yineleyicisi geçerli bölgenin üç
eleman uzunluğunda olduğunu gösteriyor: `1, 2, 3` değerleri öne taşındı.
Kalan üç konum (`yeni_son`'dan `sayilar.end()`'e kadar) hâlâ var, ama
içerikleri artık anlamlı değil. Bu ilk adımın sınıfı **değişti**: dizinin ilk
üç konumu tanımlı bir sonuca (`1, 2, 3`) sahip.

#### Kuyrukta Kalan Değerler: Belirsiz

`yeni_son`'dan `sayilar.end()`'e kadar olan konumlar `std::remove`'un
gerçekleştirme ayrıntısına bağlıdır — standart bu konumlardaki değerlerin
ne olacağını tanımlamaz, yalnız "belirlenmemiş bir değer" taşıyacaklarını
söyler. Bu ders o konumları **okumuyor**.

```cpp
// remove-kuyrugu-okumuyor.cpp — gecerli bolge disina neden bakilmiyor
#include <algorithm>
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{1, 7, 2, 7, 3, 7};

    auto yeni_son = std::remove(sayilar.begin(), sayilar.end(), 7);

    std::printf("gecerli bolge:");
    for (auto it = sayilar.begin(); it != yeni_son; ++it) {
        std::printf(" %d", *it);
    }
    std::printf("\n");
    // yeni_son'dan sayilar.end()'e kadar olan degerler okunmuyor,
    // cunku standart bu bolgedeki degerleri tanimlamiyor.
    return 0;
}
```

```text
gecerli bolge: 1 2 3
```

**KT20.** Bu ders kuyruktaki üç konumu (`yeni_son`'dan sonrası) hiçbir
`printf` çağrısında okumadı — bu, tanımsız davranıştan kaçınmak için değil
(kuyruğu okumak tanımsız davranış değildir, elemanlar hâlâ var ve
geçerlidir), ama **belirsiz** bir değeri kesin bir sayıymış gibi göstermemek
için. Standart bu konumların hangi değerleri taşıyacağını tanımlamadığından,
bu değerleri basmak beş yıl sonra farklı bir standart kütüphane
gerçeklemesinde farklı bir çıktı üretebilirdi. Bu işlemin sınıfı **belirsiz**:
kuyruk konumlarının içeriği standart tarafından garanti edilmez.

#### `erase` ile Kuyruğu Gerçekten Kaldırmak

`std::remove`'un döndürdüğü yineleyiciyi kapsayıcının kendi `erase` üye
işlevine vermek, kuyruğu gerçekten kaldırır ve kapsayıcının boyutunu
küçültür. Bu iki adımlı örüntüye **sil–kaldır deyimi** denir.

```cpp
// remove-erase-deyimi.cpp — iki adimda gercek kaldirma
#include <algorithm>
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{1, 7, 2, 7, 3, 7};

    auto yeni_son = std::remove(sayilar.begin(), sayilar.end(), 7);
    sayilar.erase(yeni_son, sayilar.end());

    std::printf("boyut %zu\n", sayilar.size());
    std::printf("icerik:");
    for (int deger : sayilar) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    return 0;
}
```

```text
boyut 3
icerik: 1 2 3
```

**KT21.** `erase(yeni_son, sayilar.end())` çağrısından sonra `sayilar.size()`
`3`'e düştü ve içerik yalnız `1, 2, 3`. Bu adımın sınıfı **değişti**: hem
boyut hem içerik, standardın tanımladığı biçimde kesin bir sonuca ulaştı.
İki adımı birleştirdiğimizde ortaya çıkan tablo şu: `remove` tek başına
kaynağı **değişti** durumuna sokar ama kuyruğu **belirsiz** bırakır; `erase`
eklendiğinde kuyruk da kaldırılır ve kaynak yeniden tam anlamıyla
**değişti** olur — artık okunmayan bir belirsiz bölge kalmaz. Bu iki adımın
neden ayrı tutulduğu da bu noktada netleşir: `remove` yalnız bir yineleyici
çiftiyle çalıştığı için kapsayıcının kendisini küçültemez; kapsayıcıyı
küçültme yetkisi yalnız o kapsayıcının kendi `erase` üye işlevinde vardır.
İki ayrı adım, iki ayrı sorumluluğu yansıtır: biri elemanları taşımayı,
diğeri belleği geri vermeyi üstlenir.

#### C++20: `std::erase` Serbest İşlevi

C++20, sık kullanılan bu iki adımı tek bir çağrıya indiren serbest işlevler
ekledi: `std::erase` ve `std::erase_if`. Bu işlevler `<vector>` başlığında
tanımlıdır ve `remove`+`erase` çiftini kapsayıcıya özel bir üye işlev
gerektirmeden uygular.

```cpp
// std-erase-serbest-islev.cpp — tek cagrida sil-kaldir
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{1, 7, 2, 7, 3, 7};

    std::erase(sayilar, 7);

    std::printf("boyut %zu\n", sayilar.size());
    std::printf("icerik:");
    for (int deger : sayilar) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    return 0;
}
```

```text
boyut 3
icerik: 1 2 3
```

**KT22.** `std::erase(sayilar, 7)` tek satırda önceki iki adımın (KT19 ve
KT21) yaptığı işi yaptı; sonuç aynı: boyut `3`, içerik `1, 2, 3`. Bu
işlemin sınıfı da **değişti** — belirsiz bir ara duruma bu kez hiç
uğranmadı, çünkü kuyruk taşıma ve gerçek kaldırma tek bir işlevin içinde,
kullanıcıya hiçbir ara yineleyici göstermeden gerçekleşti. Eski
`remove`+`erase` deyimi hâlâ derlenir ve C++20 öncesi kodda hâlâ görülür;
`std::erase`, aynı sonucu daha az kod satırıyla ve ara belirsiz durumu
gizleyerek verir.

#### Bu Kurs Saymaz: Ölçümün Ekseni Kaç Elemanın Silindiği Değildir

"Trait'ler, Genellikler ve Hata Yönetimi" dersinin (Rust müfredatındaki
`rust-soyutlama` kursu) ekseni bir gövdenin **kaç kez koştuğudur** — bir
yüklemin ya da bir kapanışın çağrı sayısını ölçer. Bu ders farklı bir
eksende duruyor: `std::erase_if`'in kaç elemanı kaldırdığı bu dersin konusu
değil. `sayilar.size()`'ın `6`'dan `3`'e düşmesi bir **sayaç** değil,
kapsayıcının şu andaki durumunu gösteren tek bir değer — tıpkı bir önceki
derste `std::count`'un döndürdüğü sayının bir çağrı sayısı değil bir sonuç
olması gibi. Bu dersin ölçtüğü şey kaldırılan eleman *sayısı* değil,
kaldırma sonrası kaynağın hangi **sınıfa** (belirsiz mi, değişti mi)
düştüğüdür.

#### `std::remove`'un Karmaşıklığı: Eşleşme Sayısından Bağımsız

`std::remove` ve `std::remove_if`, aralığı baştan sona **tek geçişte**
tarar; karmaşıklıkları O(n)'dir ve bu, aralıktaki kaç elemanın aranan
değere eşit çıktığından bağımsızdır. Algoritma hiçbir elemanı iki kez
ziyaret etmez: ya elemanı öne taşır ya da üzerinden atlar, sonra bir
sonraki konuma geçer.

```cpp
// remove-karmasikligi.cpp — tek gecis, eslesme sayisindan bagimsiz
#include <algorithm>
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> hicbiri_eslesmez{1, 2, 3, 4, 5};
    std::vector<int> hepsi_eslesir{9, 9, 9, 9, 9};

    auto son1 = std::remove(hicbiri_eslesmez.begin(),
        hicbiri_eslesmez.end(), 9);
    auto son2 = std::remove(hepsi_eslesir.begin(),
        hepsi_eslesir.end(), 9);

    std::printf("hicbiri_eslesmez gecerli uzunluk %ld\n",
        static_cast<long>(std::distance(hicbiri_eslesmez.begin(), son1)));
    std::printf("hepsi_eslesir gecerli uzunluk %ld\n",
        static_cast<long>(std::distance(hepsi_eslesir.begin(), son2)));
    return 0;
}
```

```text
hicbiri_eslesmez gecerli uzunluk 5
hepsi_eslesir gecerli uzunluk 0
```

**KT23.** İki çağrı da aynı boyuttaki (5 elemanlı) aralığı tek geçişte
tarıyor; ilkinde hiçbir eleman eşleşmediği için geçerli bölge tüm aralığı
kaplıyor (`5`), ikincisinde her eleman eşleştiği için geçerli bölge boşalıyor
(`0`). İkisinin de çalışma süresi aynı büyüklük mertebesindedir — `std::remove`
kaç eşleşme bulduğuna göre farklı bir yol izlemez, her elemanı tam olarak bir
kez ziyaret eder. Bu, algoritmanın karmaşıklığının girdi *içeriğine* değil
girdi *uzunluğuna* bağlı olduğunu gösteren bir örnek.

#### `std::list::remove`: Deyime İhtiyaç Duymayan Bir Üye İşlev

`std::vector` için `remove`+`erase` deyimi gerekliyken, `std::list` kendi
`remove` ve `remove_if` üye işlevlerini sağlar ve bunlar deyime ihtiyaç
duymaz — çağrıldıkları anda eşleşen düğümleri gerçekten bağlı listeden
çıkarırlar, ayrı bir `erase` çağrısı gerekmez.

```cpp
// list-remove-uye-islev.cpp — list kendi remove'unu saglar
#include <cstdio>
#include <list>

int main() {
    std::list<int> sayilar{1, 7, 2, 7, 3, 7};

    sayilar.remove(7);

    std::printf("boyut %zu\n", sayilar.size());
    std::printf("icerik:");
    for (int deger : sayilar) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    return 0;
}
```

```text
boyut 3
icerik: 1 2 3
```

**KT24.** `sayilar.remove(7)` tek başına, ayrı bir `erase` çağrısı olmadan,
`vector` örneğindeki iki adımlı deyimin ulaştığı sonuca ulaştı: boyut `3`,
içerik `1, 2, 3`. Bu işlemin sınıfı **değişti**. Fark, mekanizmada:
`std::list` düğümleri bağlı bir yapıda tuttuğu için bir düğümü kaldırmak
komşu düğümleri kaydırmayı gerektirmez; `remove` üye işlevi bu kaldırmayı
doğrudan yapabilir. `std::vector` ise elemanları bitişik bellekte tuttuğu
için genel `std::algorithm`'un elinde kapsayıcının boyutunu değiştirecek bir
yetki yoktur — bu yüzden `std::remove` yalnız taşıma yapar, gerçek kaldırma
kapsayıcıya özel `erase`'e kalır.

#### `std::remove_if` ve `std::erase_if`: Bir Yükleme Göre Kaldırma

`std::remove` yalnız bir değere eşitliği ölçer. Kaldırma kararı bir koşula
bağlıysa `std::remove_if` kullanılır; ilk argümanı bir değer değil, bir
yüklemdir. Deyim aynı kalır: `remove_if`'in döndürdüğü yineleyici `erase`'e
verilir, ya da C++20'de tek çağrıda `std::erase_if` kullanılır.

```cpp
// remove-if-erase-if.cpp — yukleme gore kaldirma
#include <algorithm>
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{4, 15, 8, 23, 6, 42};

    std::erase_if(sayilar, [](int deger) { return deger > 10; });

    std::printf("boyut %zu\n", sayilar.size());
    std::printf("icerik:");
    for (int deger : sayilar) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    return 0;
}
```

```text
boyut 3
icerik: 4 8 6
```

**KT25.** `std::erase_if`, yüklemi sağlayan (`10`'dan büyük olan) üç
elemanı (`15, 23, 42`) kaldırdı; kalan üç eleman girdi sırasını korudu
(`4, 8, 6`). Bu işlemin sınıfı **değişti**: boyut `6`'dan `3`'e düştü ve
standart, hangi elemanların kalacağını (yüklemi sağlamayanlar) ve bunların
göreli sırasının korunacağını tanımlar. `remove_if`+`erase` çiftiyle
`erase_if` arasındaki ilişki, `remove`+`erase` ile `erase` arasındaki
ilişkinin aynısı: ikisi de aynı iki adımı uyguluyor, `erase_if` yalnız
o adımları tek çağrıda gizliyor ve ara belirsiz kuyruğu kullanıcıya hiç
göstermiyor.

#### Kuyruk Okunursa: Çökme Değil, Tanımsız Değer

`std::remove`'un döndürdüğü `yeni_son` yineleyicisinden `sayilar.end()`'e
kadar olan konumları döngüyle okuyup basmaya çalışan bir kod **derlenir** ve
**çalışır** — burada bir derleme hatası ya da bir çökme yoktur. Sorun
çalışma zamanında da görünmez: program bir sayı basar. Sorun, o sayının
hangi standart kütüphane gerçeklemesinde hangi değeri vereceğinin
tanımsız olmasıdır.

```cpp
// kuyruk-okunursa.cpp — kuyrugu okumak neden yanlis, cokme degil
#include <algorithm>
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{1, 7, 2, 7, 3, 7};

    auto yeni_son = std::remove(sayilar.begin(), sayilar.end(), 7);

    std::printf("gecerli bolge uzunlugu %ld\n",
        static_cast<long>(std::distance(sayilar.begin(), yeni_son)));
    // Kuyruktaki degerler bilerek basilmiyor: standart onlari tanimlamiyor,
    // bu yuzden bu ders o degerlere dayanan hicbir cikti uretmiyor.
    return 0;
}
```

```text
gecerli bolge uzunlugu 3
```

Bu kod **çalışır** ama kuyruğu okumaya çalışan bir sürümünün çıktısı bu
derste hiç gösterilmiyor, çünkü o çıktı standart tarafından garanti
edilmeyen bir değere dayanır. Beş yıl sonra farklı bir derleyici ya da
farklı bir kütüphane gerçeklemesiyle koşulduğunda aynı satır farklı bir
sayı basabilir — bu, dersin başından beri kaçındığı türden bir kanıt
niteliğidir.

C++20'nin `std::erase`/`std::erase_if` serbest işlevleri yalnız `std::vector`
için değil, `std::list`, `std::deque` ve `std::forward_list` için de aşırı
yüklenmiştir. Bu, kapsayıcı türünden bağımsız ortak bir arayüz sağlar: hangi
kapsayıcıyla çalışırsanız çalışın, "bu değere/yükleme uyan elemanları kaldır"
işlemi aynı çağrıyla ifade edilir. Ancak bu ortak arayüzün altında hâlâ farklı
mekanizmalar çalışır — `std::vector` için içeride `remove`+`erase` deyimi
uygulanırken, `std::list` için doğrudan düğüm kaldırma yapılır. Arayüzün
ortaklığı, gerçekleştirme ayrıntısının aynı olduğu anlamına gelmez; bu ders
boyunca izlenen ayrım (kaynağın sınıfı: aynen, değişti, boş, belirsiz) her iki
mekanizma için de geçerli kalır, çünkü sonuç kaynağın durumuna bakılarak
değerlendirilir, uygulama ayrıntısına bakılarak değil.

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

`remove`+`erase` deyimi iki adımı ayrı ayrı görünür kılar ve aradaki
belirsiz kuyruğu bir an için ortaya çıkarır. Aynı işi tek çağrıda yapan
`std::erase` bu ara durumu hiç göstermez ve üstelik kaç eleman sildiğini
de bildirir — dönüş değeri bir yüklem çağrı sayacı değil, işlemin sonucudur:

```cpp title="erase-donen-sayi.cpp"
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{7, 1, 7, 2, 7};

    auto silinen = std::erase(sayilar, 7);

    std::printf("%zu silindi, kalan %zu\n", silinen, sayilar.size());
    return 0;
}
```

```text title="çıktı"
3 silindi, kalan 2
```

`std::erase(sayilar, 7)` beş elemanlı `sayilar` içindeki üç `7`'yi kaldırdı
ve bunu tek bir dönüş değeriyle bildirdi: `silinen` `3`, kalan boyut `2`.
Yukarıdaki `kuyruk-okunursa.cpp` örneğinde kuyruk hiçbir zaman okunmuyordu
çünkü içeriği belirsizdi; burada okunacak bir kuyruk **hiç yok** — `std::erase`
kaldırma ve boyut küçültme adımlarını kendi içinde uygulayıp bitirdiği için
kaynak doğrudan **değişti** sınıfına geçiyor, ara bir belirsiz duruma hiç
uğramıyor. Bu, yukarıda `std::list::remove` üye işlevi için görülen durumla
aynı kökten gelir: ikisi de deyimin iki adımını (taşı, sonra kısalt) kullanıcıya
göstermeden tek işlemde birleştirir; farkları yalnız mekanizmadadır —
`std::list::remove` düğüm bağlantısını değiştirirken, `std::erase` bir
`vector` üzerinde içeride `remove`+`erase` deyiminin kendisini çağırır. Adı
aynı olan `std::remove` (kaldırmayan) ile `std::list::remove` (gerçekten
kaldıran) arasındaki fark da burada tekrar görünür: ikisi de "remove" adını
taşır ama yalnız üye işlev olan gerçekten kaldırma yetkisine sahiptir.

#### Özet

- `std::remove`, elemanları kaldırmaz; aranan değere eşit olmayanları öne
  taşır ve kapsayıcının boyutunu değiştirmez. Döndürdüğü yineleyiciden
  kapsayıcının sonuna kadar olan kuyruk bölgesi **belirsiz** değer taşır
  ve bu ders o bölgeyi okumaz.
- `erase(yeni_son, end())`, kuyruğu gerçekten kaldırır ve kapsayıcının
  boyutunu küçültür; bu adımın sınıfı **değişti**.
- C++20'nin `std::erase` serbest işlevi, remove+erase deyimini tek satıra
  indirir ve aynı **değişti** sonucunu ara belirsiz duruma hiç uğramadan
  verir; kaç eleman sildiğini de dönüş değeriyle bildirir. Sınırlayıcı
  ölçüm tam olarak bunu doğrular: `std::erase(sayilar, 7)` çağrısı kuyruğa
  hiç uğramadan kaynağı doğrudan **değişti** sınıfına geçirir.
- Bu dersin merkezi ölçümünün sınıfı **belirsiz**: `std::remove` sonrası
  kuyruktaki değerlerin içeriği standart tarafından tanımlanmaz.
- `std::remove`/`std::remove_if`'in karmaşıklığı O(n)'dir ve aralıktaki kaç
  elemanın eşleştiğinden bağımsızdır; bu ders eşleşme sayısını değil kaynağın
  sınıfını ölçer.
- `std::list`, `remove`/`remove_if` üye işlevleriyle deyime ihtiyaç duymadan
  doğrudan kaldırma yapar; `std::vector`'ün genel algoritmayla neden bu
  yetkiye sahip olmadığı, kapsayıcıların bellek düzenindeki farktan gelir.

#### Sonraki Adım

Bu ders bir algoritmanın döndürdüğü yineleyiciyi kullandı ama karşılaştırma
her zaman bir değere eşitlik değildi — bazı çağrılarda bir yüklem geçirildi.
Bu yüklemler şimdiye kadar sıradan bir işlev işaretçisi ya da adlandırılmış
bir yapı olarak düşünülebilirdi, ama pratikte çoğunlukla küçük, tek kullanımlık
bir ifade olarak yazılırlar. Sonraki ders bu yüklemlerin arkasındaki
mekanizmaya, lambda ifadelerine ve işlev nesnelerine geçiyor: bir lambda
değeri **yakaladığında** o değere ne oluyor?

### Lambdalar ve Fonksiyon Nesneleri

Source: https://academia.sh/tr/kurslar/cpp-standart-kutuphane/lambdalar-ve-fonksiyon-nesneleri

Bir lambdanın mutable olmayan değerle yakalamasının kaynağı aynen bıraktığının ve yakalanan kopyanın çağrılar arasında bağımsız kaldığının ölçümü.

Önceki ders `std::remove`'a bir değer geçirerek onu aradı; ama bazı
algoritmalar bir değer yerine bir **yüklem** ister — bir koşulu
değerlendiren, çağrılabilir bir şey. Bu ders o çağrılabilir şeyin en sık
kullanılan biçimine, lambda ifadelerine geçiyor. Bir lambda dış bir
değişkeni **yakaladığında** — köşeli parantezin içine `[deger]` yazdığında —
o dış değişkene ne oluyor?

#### Değerle Yakalama: Kopyalama, Bağlama Değil

`[deger]` yazan bir lambda, `deger` değişkeninin bir **kopyasını** lambda
nesnesinin içine gömer. Bu, dış `deger`'e bir referans tutmak değildir;
lambda oluşturulduğu anda değerin bir anlık görüntüsü alınır.

```cpp
// deger-ile-yakalama.cpp — lambda disaridaki degiskeni degistiriyor mu
#include <cstdio>

int main() {
    int deger = 10;
    auto goster = [deger]() { std::printf("yakalanan deger %d\n", deger); };

    deger = 99;
    goster();

    std::printf("disaridaki deger %d\n", deger);
    return 0;
}
```

```text
yakalanan deger 10
disaridaki deger 99
```

**KT26.** Lambda oluşturulduğunda `deger` `10`'du ve lambda bu `10`'un bir
kopyasını içine gömdü. Lambda çağrılmadan önce dışarıdaki `deger` `99`'a
atandı, ama `goster()` çağrıldığında hâlâ `10` bastı — çünkü lambdanın
içindeki kopya, dışarıdaki değişkenden tamamen bağımsız. Dışarıdaki `deger`
kendisi de **aynen** kaldı: lambda oluşturma işleminin kendisi, yakaladığı
değişkeni hiç değiştirmedi; `deger = 99` satırı zaten ayrı bir atama
işlemiydi ve bu dersin ölçtüğü "yakalama" işlemi değil.

#### `mutable`: Yakalanan Kopyayı Değiştirilebilir Kılmak

Varsayılan olarak değerle yakalanan bir kopya, lambdanın çağrı işlecinin
içinde `const` sayılır — değiştirilemez. `mutable` anahtar sözcüğü bu
kısıtlamayı kaldırır ve yakalanan kopyanın lambdanın gövdesi içinde
değiştirilmesine izin verir. Bu değişiklik yalnız kopyayı etkiler, dışarıdaki
değişkeni değil.

```cpp
// mutable-yakalama.cpp — mutable ile kopyayi degistirmek
#include <cstdio>

int main() {
    int adim = 0;
    auto ilerle = [adim]() mutable {
        adim += 1;
        std::printf("lambda icindeki adim %d\n", adim);
    };

    ilerle();

    std::printf("disaridaki adim %d\n", adim);
    return 0;
}
```

```text
lambda icindeki adim 1
disaridaki adim 0
```

**KT27.** `mutable` sayesinde `adim`'in lambda içindeki kopyası `ilerle()`
çağrısı sırasında `0`'dan `1`'e değişti — lambda nesnesi kendi kopyası
üzerinde bu değişikliği yapabildi. Ama dışarıdaki `adim` çağrıdan sonra da
`0` olarak kaldı: **aynen**. Bu, `mutable`'ın neyi değiştirdiğini
netleştiriyor — dışarıdaki değişkeni değil, yalnız lambdanın kendi içine
gömdüğü kopyayı. Bu kursun merkezi ölçümü açısından bu dersin cevabı
burada: değerle yakalama, hangi `mutable` yazılırsa yazılsın, dışarıdaki
kaynağı hiçbir zaman değiştirmez.

#### Referansla Yakalama: Dış Değişkene Doğrudan Erişim

`[&deger]` yazmak, kopya almak yerine dış değişkene bir referans yakalar.
Bu durumda lambdanın gövdesindeki değişiklik dışarıdaki değişkeni de
değiştirir — çünkü artık ayrı bir kopya yoktur.

```cpp
// referans-ile-yakalama.cpp — referansla yakalama disariyi degistiriyor
#include <cstdio>

int main() {
    int adim = 0;
    auto ilerle = [&adim]() { adim += 1; };

    ilerle();

    std::printf("disaridaki adim %d\n", adim);
    return 0;
}
```

```text
disaridaki adim 1
```

**KT28.** Bu kez `mutable` yazmaya gerek kalmadı, çünkü değiştirilen şey bir
kopya değil, dışarıdaki `adim`'in kendisiydi. `ilerle()` çağrısından sonra
dışarıdaki `adim` `0`'dan `1`'e geçti: bu işlemin sınıfı **değişti**. Bu,
dersin başındaki değerle-yakalama örneğiyle doğrudan karşıt: `[deger]`
kaynağı aynen bırakırken `[&adim]` kaynağı değiştiriyor. Fark yakalama
listesindeki tek bir karakterde (`&`) ama sonucu tam tersi.

#### Toplu Yakalama: `[=]`, `[&]` ve Karma Liste

Her dış değişkeni tek tek yazmak yerine `[=]` tüm dış değişkenleri değerle,
`[&]` tümünü referansla yakalar. İkisi karışık da yazılabilir: `[=, &deger]`
diğer her şeyi değerle, yalnız `deger`'i referansla yakalar; `[&, deger]`
ise tam tersini yapar. Toplu yakalama, hangi değişkenin yakalanacağını
derleyiciye bırakır — lambda gövdesinde kullanılan her dış ad otomatik
olarak listeye eklenir.

```cpp
// toplu-yakalama.cpp — karma yakalama listesi
#include <cstdio>

int main() {
    int esik = 10;
    int adim = 0;

    auto degerlendir = [=, &adim](int deger) {
        if (deger > esik) {
            adim += 1;
        }
    };

    degerlendir(15);

    std::printf("disaridaki adim %d\n", adim);
    std::printf("disaridaki esik %d\n", esik);
    return 0;
}
```

```text
disaridaki adim 1
disaridaki esik 10
```

**KT29.** `[=, &adim]` yakalama listesinde `esik` değerle (kopya olarak),
`adim` ise referansla yakalandı. `degerlendir(15)` çağrısı `esik`'in
kopyasını okudu ama değiştirmedi — dışarıdaki `esik` **aynen** kaldı.
Aynı çağrı `adim`'i referans üzerinden **değiştirdi**: dışarıdaki `adim`
`0`'dan `1`'e geçti. Karma yakalama listesi, aynı lambda içinde bazı
değişkenlerin aynen kalmasını, bazılarının değişmesini aynı anda mümkün
kılar — sınıf kararı yakalama listesindeki her ad için ayrı ayrı verilir,
listenin tamamı için tek bir sınıf yoktur.

#### Lambda Aslında Bir İşlev Nesnesidir

Derleyici bir lambda ifadesi gördüğünde, arkada isimsiz bir sınıf üretir:
bu sınıfın bir `operator()` üye işlevi vardır ve yakalanan değişkenler bu
sınıfın veri üyeleridir. `[deger]` yakalaması, üretilen sınıfa `deger`
tipinde bir üye ekler ve o üyeyi yapıcıda kopyalar. Bu, lambdaların
elle yazılmış işlev nesneleriyle (bir `operator()` tanımlayan sıradan
sınıflar) neden aynı yerlerde kullanılabildiğini açıklar.

```cpp
// islev-nesnesi-esdegeri.cpp — lambda ile elle yazilan sinif ayni isi yapiyor
#include <cstdio>

struct DegerGosterici {
    int deger;
    void operator()() const { std::printf("sinif icindeki deger %d\n", deger); }
};

int main() {
    int deger = 42;

    DegerGosterici gosterici_sinif{deger};
    auto gosterici_lambda = [deger]() { std::printf("lambda icindeki deger %d\n", deger); };

    gosterici_sinif();
    gosterici_lambda();
    return 0;
}
```

```text
sinif icindeki deger 42
lambda icindeki deger 42
```

**KT30.** `DegerGosterici` sınıfı ile `gosterici_lambda` lambdası aynı
sonucu üretti, çünkü ikisi de aynı örüntüyü uyguluyor: bir değeri bir üye
olarak taşımak ve `operator()` içinde onu okumak. Bu çağrılardan hiçbiri
`deger`'i değiştirmedi — ikisi de **aynen** bırakıyor, çünkü ikisi de kendi
kopyalarını yapıcıda aldılar ve yalnız okudular. Bu denklik, lambdaların
"özel" bir dil özelliği değil, derleyicinin otomatik olarak yazdığı sıradan
bir sınıf olduğunu gösteriyor.

#### Yakalama Listesi Kaynağa Ne Bırakıyor

"C ile Sistem Programlama" kursu (`c-sistem-programlama`), bir değişikliğin
karşı tarafa **ulaşıp ulaşmadığını** sordu: yazanın kendi kopyası mı, aynı
nesneyi paylaşan taraflar mı, yoksa hiç kimse mi. Yakalama listesi o soruyu
hemen akla getirir — `[&deger]` ile yakalayan lambda dışarıdaki değişikliği
"görür", `[deger]` ile yakalayan görmez. Bu ders o soruyu sormuyor. Burada
ölçülen, lambda çağrıldıktan sonra **kaynağın kendisinde** ne kaldığıdır:
değerle yakalamada kaynak **aynen**, referansla yakalamada **değişti**, ilk
değerli yakalamayla taşındığında **belirsiz**. Görmek bir ilişkiyi, geride
kalan ise bir durumu adlandırır; bu kursun ölçüsü durumdur.

#### `std::function`: Farklı Çağrılabilirleri Aynı Tipte Tutmak

Her lambda, derleyicinin ürettiği kendine özgü, adı olmayan bir tipe
sahiptir — iki farklı lambda ifadesi, aynı imzayı taşısa bile farklı
tiplerdir. `std::function<Sonuc(Argumanlar...)>`, bu farklı tipleri tek bir
ortak kapta tutmayı sağlayan bir sarmalayıcıdır. Sarmalama işlemi, sarmalanan
çağrılabilirin kendisini değiştirmez; yalnız onun bir kopyasını taşır.

```cpp
// std-function-sarmalama.cpp — std::function sarmaladigi lambdayi degistiriyor mu
#include <cstdio>
#include <functional>

int main() {
    int carpan = 3;
    auto lambda = [carpan](int deger) { return deger * carpan; };

    std::function<int(int)> sarmali = lambda;
    carpan = 100;

    std::printf("sarmali(5) sonucu %d\n", sarmali(5));
    std::printf("disaridaki carpan %d\n", carpan);
    return 0;
}
```

```text
sarmali(5) sonucu 15
disaridaki carpan 100
```

**KT31.** `std::function<int(int)> sarmali = lambda;` satırı, `lambda`'nın
o anki durumunun bir kopyasını `sarmali`'nin içine aldı — bu, tıpkı `[carpan]`
yakalamasının kendisi gibi bir kopyalama işlemi. `carpan` daha sonra `100`'e
atansa da `sarmali(5)` hâlâ `15` verdi (`5 * 3`), çünkü `sarmali`'nin içindeki
kopya `carpan`'ın atama öncesindeki değerini taşıyordu. `lambda`'nın kendisi
bu sarmalama işleminden **aynen** çıktı: `std::function`'a atama, `lambda`
nesnesine hiç yazmadı, yalnız onu kopyaladı. Bu, dersin başındaki değerle
yakalama örneğiyle aynı örüntü — fark, bu kez kopyalanan şeyin bir `int`
değil, bir lambda nesnesinin kendisi olması.

Bu kopyalamanın bir bedeli vardır: `std::function`, farklı tiplerdeki
çağrılabilirleri tek bir ortak tipte tutabilmek için **tip silme** (type
erasure) uygular — sarmaladığı çağrılabilirin gerçek tipini bir taban sınıf
arkasına gizler. Bu gizleme genellikle bir dinamik bellek ayırma gerektirir
ve her çağrıda dolaylı bir zıplama (sanal çağrı benzeri bir mekanizma) ekler.
Doğrudan bir `auto` değişkenine atanan lambda ise kendi somut tipini korur;
derleyici çağrıyı doğrudan satır içine gömebilir. `std::function` bu yüzden
her yerde `auto`'nun yerini tutacak bir araç değildir — farklı tiplerdeki
çağrılabilirleri aynı kapta taşımak gerektiğinde (örneğin bir kapsayıcıda
callback listesi tutarken) tercih edilir, yalnızca tek bir lambdayı tutmak
için değil.

#### Genel Lambda: Parametre Tipi Olarak `auto`

C++14'ten itibaren bir lambdanın parametre listesinde somut bir tip yerine
`auto` yazılabilir. Bu, lambdanın çağrı işlecini bir şablona dönüştürür:
aynı lambda, farklı tiplerle çağrıldığında her tip için ayrı bir
`operator()` örneği üretilir. Bu, yakalama listesiyle ilgisizdir — yakalanan
değişkenlerin sınıfı (aynen/değişti) yine aynı kurallarla belirlenir; genel
olan yalnızca çağrı parametresidir.

```cpp
// genel-lambda.cpp — auto parametreli lambda farkli tiplerle calisiyor
#include <cstdio>

int main() {
    auto goster = [](auto deger) {
        std::printf("deger olarak goster cagrildi\n");
        (void)deger;
    };

    goster(7);
    goster(3.14);

    return 0;
}
```

```text
deger olarak goster cagrildi
deger olarak goster cagrildi
```

**KT32.** `goster` lambdası hem `int` hem `double` ile çağrıldı; derleyici
her çağrı için ayrı bir `operator()` örneği üretti, ama kaynak kodda tek bir
lambda ifadesi var. Bu, şablon işlevlerin genel işleyişiyle aynı mekanizma —
`auto` parametre, lambdanın çağrı işlecini bir şablon üye işlevine çevirir.
Bu dersin merkezi ekseniyle (yakalanan değişkenin sınıfı) doğrudan ilgisi
yok; genel lambda yalnızca *çağrı parametresinin* tipini esnekleştirir,
yakalama listesinin davranışını değiştirmez.

#### `mutable` Yazılmazsa

`mutable` olmayan bir lambdanın gövdesinde, değerle yakalanan bir
değişkene doğrudan atama yapmaya çalışan bir satır **derlenmez** — çünkü
`operator()`, `mutable` yazılmadığı sürece `const` olarak üretilir ve
`const` bir üye işlev içinde veri üyelerine atama yapılamaz.

```cpp
// const-lambda-govdesi.cpp — mutable olmadan atama neden derlenmiyor
#include <cstdio>

int main() {
    int deger = 0;
    auto oku = [deger]() { std::printf("deger %d\n", deger); };

    // Asagidaki satir derlenmez, cunku operator() const ve deger degistirilemez:
    // auto bozuk = [deger]() { deger += 1; };

    oku();
    return 0;
}
```

```text
deger 0
```

Bu kısıtlama bir eksiklik değil, güvencedir: `mutable` yazılmadığı sürece
bir lambdanın aynı yakalanan değeri her çağrıda aynı biçimde okuyacağı,
çağrılar arasında sessizce bir durum biriktirmeyeceği derleme anında
zorunlu kılınır. Bir lambdanın çağrılar arası durum tutması isteniyorsa bu
niyet `mutable` yazılarak açıkça belirtilmelidir.

Bu güvence, okuyan tarafın lambdayı incelerken hangi soruyu sorması
gerektiğini de belirler: yakalama listesinde `&` var mı yok mu, ve varsa
`mutable` yazılmış mı. Bu iki soruya verilen yanıt, o lambdanın yakaladığı
her değişken için kaynağın **aynen** mi yoksa **değişti** mi kalacağını
tek başına belirler; ayrıca bir çalışma zamanı gözlemine gerek kalmaz.

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

Üç lambda da aynı gövdeyi taşır — yakalanan adı bir kez artırıp yazdırır —
ama yalnız yakalama biçimi değiştiğinde dışarıdaki kaynağın sınıfı üçe
ayrılır:

```cpp title="uc-yakalama-uc-sinif.cpp"
#include <cstdio>
#include <string>
#include <utility>

int main() {
    int adim = 0;
    auto degerle = [adim]() mutable {
        ++adim;
        std::printf("govde icinde: %d\n", adim);
    };
    degerle();

    int konum = 0;
    auto referansla = [&konum]() mutable {
        ++konum;
        std::printf("govde icinde: %d\n", konum);
    };
    referansla();

    std::string s = "veri";
    auto tasiyarak = [t = std::move(s)]() {
        std::printf("govde icinde: %s\n", t.c_str());
    };
    tasiyarak();
    s = "yeni-deger";

    std::printf("disaridaki adim: %d\n", adim);
    std::printf("disaridaki konum: %d\n", konum);
    std::printf("disaridaki s: %s\n", s.c_str());
    return 0;
}
```

```text title="çıktı"
govde icinde: 1
govde icinde: 1
govde icinde: veri
disaridaki adim: 0
disaridaki konum: 1
disaridaki s: yeni-deger
```

Üç yakalama da gövdenin içinde beklendiği gibi çalışır, ama dışarıdaki
kaynağa bıraktıkları iz üç farklı sınıfa düşer. `[adim]` değerle yakalar;
`mutable` kopyanın artırılmasına izin verse de bu artış yalnız lambdanın
kendi kopyasını etkiler, dışarıdaki `adim` **aynen** `0` kalır. `[&konum]`
referansla yakalar; gövdenin içindeki `++konum` doğrudan dışarıdaki
değişkeni değiştirir, dışarıdaki `konum` **değişti** ve `1` oldu. `[t =
std::move(s)]` ilk değerli bir yakalamadır: `s`'nin tuttuğu kaynak `t`'ye
taşınır ve taşıma sonrası `s` geçerli ama **belirsiz** bir durumda kalır —
bu yüzden bu ders `s`'nin taşıma sonrası içeriğini ya da uzunluğunu hiç
okumaz ve yazdırmaz. `s`'yi tekrar kullanılabilir kılan şey ona yeni bir
değer atamaktır: `s = "yeni-deger"` satırından sonra `s` artık tanımlı bir
içeriğe sahiptir ve onu okumak güvenlidir — çıktıdaki son satır bunu
gösterir. Aynı gövde, üç farklı yakalama biçimiyle kaynağı üç farklı
sınıfa bırakır: aynen, değişti, belirsiz.

#### Özet

- `[deger]` ile değerle yakalama, dış değişkenin bir kopyasını alır; dış
  değişken lambda oluşturma işleminden **aynen** çıkar.
- `mutable`, yakalanan kopyayı lambda gövdesi içinde değiştirilebilir kılar,
  ama bu değişiklik yalnız kopyayı etkiler, dışarıdaki kaynağı değil.
- `[&deger]` ile referansla yakalama, dış değişkenin kendisine erişir; bu
  durumda lambda gövdesindeki değişiklik dışarıyı da **değiştirir**.
- Bir lambda, derleyicinin ürettiği isimsiz bir sınıfın örneğidir; yakalanan
  değişkenler bu sınıfın veri üyeleri, lambdanın gövdesi ise `operator()`'dür.
- Bu dersin merkezi ölçümünün sınıfı **aynen**: `mutable` olsun ya da
  olmasın, değerle yakalama dışarıdaki kaynağı hiçbir zaman değiştirmez.
- Sınırlayıcı ölçüm: aynı gövde üç farklı yakalamayla üç farklı sınıf üretir
  — değerle yakalama **aynen**, referansla yakalama **değişti**, ilk değerli
  (taşıyan) yakalama kaynağı **belirsiz** bırakır ve bu ders o kaynağı yeniden
  atamadan önce hiç okumaz.

#### Sonraki Adım

Bu ders bir lambdanın yakaladığı değeri kendi içinde nasıl taşıdığını,
`mutable`'ın bu kopyayı ne zaman değiştirilebilir kıldığını ve referansla
yakalamanın neden dışarıdaki kaynağı doğrudan etkilediğini gösterdi.
Sonraki ders bir kapsayıcının elemanlarını dönüştürmeden, yeni bir
kapsayıcı oluşturmadan görüntülemenin yolunu ele alıyor: bir aralık görünümü
(view), altındaki kapsayıcıya bakan bir pencere olarak kaynağı nasıl aynen
bırakıyor?

### Aralıklar

Source: https://academia.sh/tr/kurslar/cpp-standart-kutuphane/araliklar

Bir sol değerden kurulan görünümün altındaki kapsayıcıyı aynen bıraktığının ve görünüm zincirinin tembel değerlendirildiğinin ölçümü.

Önceki ders bir lambdanın değerle yakaladığı bir değişkenin kopyasını
kendi içine gömdüğünü ve dışarıdaki kaynağı **aynen** bıraktığını gösterdi.
Bu ders C++20'nin aralık kütüphanesine (`std::ranges`) geçiyor ve benzer bir
soru soruyor: bir **görünüm** (view), altındaki bir kapsayıcıdan kurulduğunda,
o kapsayıcıyı kopyalıyor mu, yoksa yalnız ona bakan bir pencere mi oluyor?

#### Bir Görünüm Kapsayıcıyı Kopyalamaz

`std::views::filter`, verilen bir aralıktan yalnız bir koşulu sağlayan
elemanları gösteren bir görünüm üretir. Bu görünüm kendi elemanlarını
tutmaz; her gezinildiğinde altındaki aralığa bakar.

```cpp
// gorunum-kopyalamaz.cpp — filter altindaki vector'u kopyaliyor mu
#include <cstdio>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> sayilar{1, 2, 3, 4, 5, 6};

    auto ciftler = sayilar | std::views::filter(
        [](int deger) { return deger % 2 == 0; });

    sayilar.push_back(8);

    std::printf("gorunumdeki elemanlar:");
    for (int deger : ciftler) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    std::printf("kaynak vector boyutu %zu\n", sayilar.size());
    return 0;
}
```

```text
gorunumdeki elemanlar: 2 4 6 8
kaynak vector boyutu 7
```

**KT33.** `ciftler` görünümü oluşturulduktan **sonra** `sayilar`'a `8`
eklendi ve görünüm gezinildiğinde bu yeni eleman da listeye girdi (`8` çift
olduğu için filtreyi geçti). Bu, `ciftler`'in `sayilar`'ın bir kopyasını
almadığının kanıtı — kopya alsaydı, `push_back`'ten sonraki değişiklik
görünümde görünmezdi. `sayilar`'ın kendisi bu görünüm oluşturma işleminden
**aynen** çıktı: `views::filter` çağrısı kaynağa hiç yazmadı, yalnız ona
bakan bir tanım kurdu. `push_back` ayrı bir işlemdi ve bu dersin ölçtüğü
"görünüm kurma" adımı değil.

#### Görünüm Zinciri Tembel Değerlendirilir

Birden çok görünüm `|` işleciyle zincirlenebilir. `std::views::transform`,
her elemana bir işlev uygulayan bir görünüm üretir. Bir zincir kurulduğunda,
hiçbir eleman hemen işlenmez — işlem yalnız zincir gezinildiğinde, eleman
eleman gerçekleşir.

```cpp
// gorunum-zinciri-tembel.cpp — filter ve transform ne zaman calisiyor
#include <cstdio>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> sayilar{1, 2, 3, 4, 5, 6};

    auto zincir = sayilar
        | std::views::filter([](int deger) { return deger % 2 == 0; })
        | std::views::transform([](int deger) {
              std::printf("donusturuluyor: %d\n", deger);
              return deger * 10;
          });

    std::printf("zincir kuruldu\n");

    for (int deger : zincir) {
        std::printf("uretilen deger %d\n", deger);
    }
    return 0;
}
```

```text
zincir kuruldu
donusturuluyor: 2
uretilen deger 20
donusturuluyor: 4
uretilen deger 40
donusturuluyor: 6
uretilen deger 60
```

**KT34.** "zincir kuruldu" satırı basıldığında `transform`'un içindeki
`printf` hiç çalışmamıştı — `filter` ve `transform` çağrıları o ana kadar
hiçbir elemanı işlemedi, yalnız bir tanım kurdular. "donusturuluyor" satırı
ilk kez `for` döngüsü zinciri gezinmeye başladığında, tam da ilk elemanın
sırası geldiğinde göründü, ve her "donusturuluyor" satırı kendi "uretilen
deger" satırından hemen önce, teker teker çıktı — bir eleman işlenip
yazdırılmadan bir sonrakine geçilmedi. Çıktının bu sırası (önce "zincir
kuruldu", sonra eleman eleman iç içe geçen "donusturuluyor"/"uretilen deger"
çiftleri) zincirin ne zaman çalıştığını gösteriyor: kurulma anında değil,
gezinme sırasında, eleman eleman. Kaynak `sayilar` bu zincirin hiçbir
adımında değişmedi — **aynen**.

#### `filter` ve `transform`: Bir İşlem Hattı

İki görünümün zincirlenmesi, bir işlem hattı kurar: `filter` önce hangi
elemanların geçeceğine karar verir, `transform` yalnız geçen elemanlara
uygulanır. Bu sıra önemlidir — sıra değişse zincirin ürettiği sonuç da
değişebilir.

```cpp
// filter-transform-hatti.cpp — sira uretilen sonucu nasil etkiliyor
#include <cstdio>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> sayilar{1, 2, 3, 4, 5};

    auto once_filtrele_sonra_donustur = sayilar
        | std::views::filter([](int deger) { return deger % 2 == 0; })
        | std::views::transform([](int deger) { return deger * deger; });

    std::printf("once filtrele sonra donustur:");
    for (int deger : once_filtrele_sonra_donustur) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    return 0;
}
```

```text
once filtrele sonra donustur: 4 16
```

**KT35.** Zincir önce çift sayıları (`2, 4`) seçti, sonra onları kareledi
(`4, 16`). `sayilar`'ın kendisi bu işlem hattı boyunca hiç değişmedi —
`sayilar` hâlâ `{1, 2, 3, 4, 5}`; **aynen**. `once_filtrele_sonra_donustur`
adı, zincirin okunma sırasını (soldan sağa, `filter` önce) açıkça
belirtiyor; bu, aralık zincirlerinin okunmasında kafa karışıklığını önleyen
bir alışkanlık — zincir uzadıkça hangi adımın önce çalıştığını isimden değil
zincirin kendi sırasından okumak gerekir.

#### Görünüm Kurmak Kaynağa Ne Bırakıyor

"C ile Sistem Programlama" kursu (`c-sistem-programlama`), bir değişikliğin
karşı tarafa **ulaşıp ulaşmadığını** sordu. Görünümler bu soruyu hemen
akla getirir: bir görünüm elemanları kendisi tutmaz, dolaşıldığında altındaki
kapsayıcıdan okur. Ama bu ders "görünüm sonradan yapılan değişikliği görür
mü" sorusunu ölçü olarak kullanmaz. Burada ölçülen, görünümü **kurmanın**
kaynağa ne bıraktığıdır: sol değer bir kapsayıcıdan kurulan `views::filter`,
`views::transform` ve `views::take` kaynağa yazmaz, kaynak **aynen** kalır.
Sınırlayıcı ölçümde göreceğimiz gibi sağ değerden kurulan görünüm için yanıt
başkadır.

#### `views::take`: Bir Aralığın İlk N Elemanı

`std::views::take`, verilen bir aralığın yalnız ilk `n` elemanını gösteren
bir görünüm üretir. Diğer görünümler gibi bu da altındaki kapsayıcıyı
kopyalamaz; yalnız gezinmeyi `n` eleman sonra durduran bir sınır kurar.

```cpp
// views-take.cpp — take altindaki kapsayiciyi kopyaliyor mu
#include <cstdio>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> sayilar{10, 20, 30, 40, 50};

    auto ilk_uc = sayilar | std::views::take(3);

    std::printf("ilk uc:");
    for (int deger : ilk_uc) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    std::printf("kaynak vector boyutu %zu\n", sayilar.size());
    return 0;
}
```

```text
ilk uc: 10 20 30
kaynak vector boyutu 5
```

**KT36.** `ilk_uc` görünümü yalnız ilk üç elemanı gösterdi, ama `sayilar`
işlem sonunda hâlâ beş eleman taşıyor — boyutu değişmedi. `views::take`,
`views::filter`'la aynı sınıfa giriyor: **aynen**. Fark, `filter`'ın hangi
elemanların geçeceğine bir koşulla karar vermesi, `take`'in ise yalnız
kaç elemanın geçeceğine bir sayıyla karar vermesi. İkisi de altındaki
`sayilar`'a yazmadan, ona bakan farklı bir pencere kuruyor — bu, aralık
görünümlerinin ortak özelliği: hepsi kaynağı aynen bırakır, yalnız o
kaynağın hangi kısmının ya da hangi biçiminin görüneceğini değiştirir.

#### `views::drop`: Bir Aralığın Baştaki N Elemanını Atlamak

`std::views::take`'in tersi `std::views::drop`'tur: verilen aralığın ilk
`n` elemanını atlayıp gerisini gösteren bir görünüm üretir. İkisi de
altındaki kapsayıcıyı kopyalamaz, yalnız gezinmenin nereden başlayacağını
ya da nerede duracağını belirleyen bir sınır kurar.

```cpp
// views-drop.cpp — drop altindaki kapsayiciyi kopyaliyor mu
#include <cstdio>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> sayilar{10, 20, 30, 40, 50};

    auto son_iki = sayilar | std::views::drop(3);

    std::printf("son iki:");
    for (int deger : son_iki) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    std::printf("kaynak vector boyutu %zu\n", sayilar.size());
    return 0;
}
```

```text
son iki: 40 50
kaynak vector boyutu 5
```

**KT37.** `son_iki` görünümü ilk üç elemanı (`10, 20, 30`) atladı ve
kalan iki elemanı (`40, 50`) gösterdi; `sayilar` işlem sonunda hâlâ beş
eleman taşıyor. `views::take` ve `views::drop` birbirini tamamlayan iki
görünüm: biri "ilk n'i göster", diğeri "ilk n'i atla" der, ikisi de kaynağı
**aynen** bırakır. `sayilar | std::views::take(3)` ile `sayilar |
std::views::drop(3)` art arda gezinilse, birbirini tamamlayan iki parça
(`10, 20, 30` ve `40, 50`) elde edilir — aynı kaynağın iki farklı, ama
birbiriyle örtüşmeyen görünümü.

#### `std::ranges::sort`: Yineleyici Çifti Yerine Doğrudan Aralık

Önceki derste görülen `std::sort`, bir yineleyici çifti (`begin()`,
`end()`) ister. C++20'nin `std::ranges::sort`'u aynı işi doğrudan bir
aralık nesnesiyle yapar — `sayilar.begin()`/`sayilar.end()` yazmaya gerek
kalmaz. Sonuç aynı: kapsayıcı yerinde yeniden sıralanır.

```cpp
// ranges-sort.cpp — ranges::sort yineleyici cifti istemiyor
#include <algorithm>
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sayilar{5, 3, 1, 4, 2};

    std::ranges::sort(sayilar);

    std::printf("siralanmis:");
    for (int deger : sayilar) {
        std::printf(" %d", deger);
    }
    std::printf("\n");
    return 0;
}
```

```text
siralanmis: 1 2 3 4 5
```

**KT38.** `std::ranges::sort(sayilar)` çağrısı, `sayilar`'ı yerinde
yeniden sıraladı — bu işlemin sınıfı, önceki dersteki `std::sort` gibi
**değişti**. Aradaki fark bir çağrı kolaylığı: `std::ranges::sort` bir
aralık nesnesini doğrudan kabul eder, `begin()`/`end()` çağrılarını
kendi içinde yapar. Bu, `std::ranges` ad alanındaki algoritmaların ortak
bir özelliği — aynı iş, aralık nesneleriyle daha az yazımla ifade edilir;
altta yatan karmaşıklık ve sonucun sınıfı (**değişti**/​**aynen**)
değişmez.

#### Görünüm Kurmanın Karmaşıklığı: O(1), Gezinmenin Karmaşıklığı: Değişken

Bir görünüm zinciri kurmak (`sayilar | std::views::filter(...) |
std::views::transform(...)`) sabit zamanlıdır — O(1). Bu satır çalıştığında
hiçbir eleman ziyaret edilmez, hiçbir bellek ayrılmaz; kurulan şey yalnızca
kaynağa bakan bir tanım, bir dizi lambda ve birkaç sınır değeridir. Asıl iş,
zincir gezinildiğinde başlar ve orada karmaşıklık bağdaştırıcıya göre değişir.

`views::transform` ve `views::filter` ile bir sonraki elemana geçmek
(`operator++`) her ikisinde de O(1) — ama `filter`'ın O(1)'i "amorti
edilmiş" bir O(1)'dir: predikat elemanı reddederse, bir sonraki kabul edilen
elemana ulaşana kadar iç döngü döner. En kötü durumda (hiçbir eleman
predikatı geçmiyorsa) bir tek `operator++` çağrısı O(n) sürebilir; ama
zincir tümüyle gezinildiğinde toplam maliyet yine O(n)'dir, çünkü her eleman
en fazla bir kez ziyaret edilir. `views::take` ve `views::drop` ise gerçek
O(1): `take`, kaç eleman kaldığını sayar ve o sayıya ulaşınca durur;
`drop`, rastgele erişimli bir aralık üzerinde (ör. `vector`) başlangıç
noktasını doğrudan `n` adım ileri taşır, tek tek adımlamaz.

Bu ayrım, zincire hangi bağdaştırıcının eklendiğinin performans üzerinde görünmez
bir etkisi olduğunu gösteriyor: kurma maliyeti her zaman O(1), ama gezinme
maliyeti bağdaştırıcının türüne ve predikatın seçiciliğine bağlı. Bu dersin
ölçümü — kaynağın **aynen** kaldığı — bu karmaşıklık farkından bağımsızdır;
bir görünüm ister O(1) ister amorti edilmiş O(1) ile gezinsin, altındaki
kapsayıcıyı hiçbir durumda değiştirmez. Karmaşıklık farkı yalnızca ne kadar
sürdüğünü etkiler, kaynağın hangi sınıfa girdiğini değil.

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

Bu derste görünümlerin merkezi ölçümü **aynen**: sol değerden kurulan bir
görünüm altındaki kapsayıcıyı hiçbir aşamada değiştirmez. Aynı kurma işlemini
küçük bir değişiklikle — kaynağı bir sol değer yerine bir **sağ değer**
olarak vererek — tekrarlamak sınıfı değiştirir. `std::move(v)` bir
`std::vector<int>&&` üretir; bunu `std::views::transform`'a yollamak, C++20'nin
sahiplenen görünümünü (`std::ranges::owning_view`) devreye sokar: bağdaştırıcı,
kendine verilen sağ değeri **taşıyan yapıcıyla** kendi içine alır. `v`'nin
tuttuğu tampon (buffer) kopyalanmaz, sahiplenen görünümün kendi üyesine
geçer; bu geçiş sabit zamanlıdır ve elemanlara tek tek dokunmaz. Sonuçta `v`
taşınmış bir nesne olarak kalır — `vector`'ın taşıyan yapıcısı için bilinen
davranış boş bir `vector` bırakmaktır, yani `v.empty()` artık `evet` verir.
Kaynağın sınıfı **boş**'a düşer; oysa aynı zincir bir sol değerden kurulsaydı
sınıf **aynen** kalırdı.

```cpp
// sag-degerden-owning-view.cpp — sag deger vector owning_view icine tasinir
#include <cstdio>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> v{10, 20, 30};

    auto katlanmis = std::move(v) | std::views::transform([](int deger) { return deger; });

    std::printf("gorunum:");
    for (int deger : katlanmis) {
        std::printf(" %d", deger);
    }
    std::printf("\n");

    std::printf("v bos mu: %s\n", v.empty() ? "evet" : "hayir");
    return 0;
}
```

```text
gorunum: 10 20 30
v bos mu: evet
```

Görünüm yine `10 20 30` veriyor, çünkü elemanlar hiçbir yere kopyalanmadı;
yalnız kimin sahip olduğu değişti. `v` artık boş olsa da sahiplenen görünüm
kendi içine taşıdığı tamponu tutmaya devam ediyor ve gezinme onun üzerinden
çalışıyor. Bu boşalmanın dayanağı **taşıyan yapıcı**dır — taşıyan **atama**
için aynı sonucu varsaymamak gerekir, çünkü atama zaten var olan bir hedefin
üzerine yazar ve kaynağı boşaltıp boşaltmaması ayrı bir garantidir.
Sahiplenen görünüm, adı üstünde, taşıdığı nesnenin ömrünü kendi ömrüne
bağlar; bu yüzden yukarıdaki örnek güvenlidir ve kod olarak koşturulabilir.
Buna karşılık, bir görünümün *kapsayıcıya bakan bir referans* tuttuğu ve o
kapsayıcının ömrü görünümden önce bittiği bir durum tanımsız davranış
üretir — böyle bir örnek burada yazılmaz ve koşturulmaz, çünkü tanımsız
davranışın kendisi bu dersin konusu değil, yalnız var olduğunu bilmek
önemlidir. C++20'nin `owning_view`'i tam olarak bu riski ortadan kaldırmak
için tasarlandı: sağ değerden kurulan bir görünüm artık bir referans değil,
kaynağın kendisini taşıyarak sahiplenir.

#### Özet

- Bir görünüm (`std::views::filter`, `std::views::transform`) altındaki
  kapsayıcıyı kopyalamaz; kaynak kapsayıcı görünüm kurma işleminden
  **aynen** çıkar.
- Görünüm zinciri tembel değerlendirilir: elemanlar zincir kurulduğunda
  değil, gezinildiğinde tek tek üretilir.
- `filter` ve `transform` birlikte bir işlem hattı kurar; zincirdeki sıra
  hangi adımın önce uygulandığını belirler.
- Bu dersin merkezi ölçümünün sınıfı **aynen**: bir görünüm, sol değerden
  kurulduğunda altındaki kapsayıcıyı hiçbir aşamada değiştirmez.
- Sınırlayıcı ölçüm: aynı kurma işlemi sol değer yerine sağ değerle
  yapıldığında sınıf **boş**'a düşer — `std::move(v) | std::views::transform(...)`
  bir sahiplenen görünüm (`owning_view`) kurar, `v`'yi taşıyan yapıcıyla
  kendi içine alır ve `v.empty()` `evet` olur; görünüm yine `10 20 30` verir.

#### Sonraki Adım

Bu derste geçen `auto zincir = sayilar | std::views::filter(...) | ...`
satırı, `zincir`'in gerçek tipini hiç yazmadı — o tip, `filter` ve
`transform`'un iç içe geçmiş, adı olmayan bir görünüm tipi. Sonraki ders bu
kursun kapanışı: `auto` ve `decltype`'ın bu tür tipleri nasıl çıkardığını ve
tip çıkarımının kaynağa ne bıraktığını ele alıyor.

### Tip Çıkarımı

Source: https://academia.sh/tr/kurslar/cpp-standart-kutuphane/tip-cikarimi

auto'nun const ve referansı düşürmesi, auto&/const auto&/auto&&, decltype ve decltype(auto), yapısal bağlamada kopya-referans farkı ve tip çıkarımının kaynağa ne bıraktığı.

Önceki ders bir görünümün lvalue'dan kurulunca kapsayıcıyı **aynen** bıraktığını, rvalue'dan
kurulunca taşıyan yapıcının kapsayıcıyı **boş**a çevirdiğini gösterdi. Her iki örnekte de
`auto` bir yerde duruyordu — görünümün gerçek tipini yazmak yerine derleyiciye bıraktık. Bu
ders o kısayolun ne yaptığını açıyor: `auto` bir ifadenin tipini nasıl çıkarır, hangi durumda
`const`'u ve referansı düşürür, hangi yazımla düşürmez. Kursun ölçtüğü soru burada da aynı:
bir tip çıkarımı, kaynağa ne bırakır?

#### auto Neyi Atar, Neyi Düşürür

`auto` bir başlatıcı ifadesinden tip çıkarır; ama çıkardığı tip, ifadenin tipiyle birebir
aynı değildir. Kural şu: `auto` kendi başına yazıldığında ifadenin **const'unu ve
referansını düşürür**, geriye yalnız temel tip kalır. Aşağıdaki örnek bunu gösteriyor.

```cpp
// auto-dusurme.cpp — auto const ve referansi nasil dusuruyor
#include <cstdio>

int main() {
    const int sabit = 42;
    int deger = 10;
    int& referans = deger;

    auto a = sabit;      // int -- const dustu
    auto b = referans;   // int -- referans dustu

    a = 99;
    b = 99;

    std::printf("sabit %d, deger %d\n", sabit, deger);
    std::printf("a %d, b %d\n", a, b);
    return 0;
}
```

```text
sabit 42, deger 10
a 99, b 99
```

`a` ve `b`, `sabit` ile `referans`ın birer kopyasıdır; ikisine de yazmak kaynağı etkilemez.
`sabit` 42'de, `deger` 10'da kalıyor. **KT39.** `auto`, bir ifadenin const'unu ve
referansını düşürerek bağımsız bir kopya üretir; kopyaya yazmak kaynağa hiçbir şey
bırakmaz — kaynak **aynen** kalır, çünkü kopyanın kendisi ayrı bir nesnedir.

#### auto& ile Referansa Bağlanmak

`auto&` yazıldığında referans düşmez; çıkarılan tip başlatıcının kendisine bağlanan bir
referanstır. Bu, `auto`'nun tam tersi bir davranış: kopya değil, kaynağın kendisi.

```cpp
// auto-referans.cpp — auto& bir kopyaya degil kaynagin kendisine baglanir
#include <cstdio>

int main() {
    int deger = 10;
    int& referans = deger;

    auto& baglanti = referans;   // int& -- referans dusmuyor
    baglanti = 99;

    std::printf("deger %d\n", deger);
    return 0;
}
```

```text
deger 99
```

`baglanti` üzerinden yapılan atama `deger`e ulaşıyor; `auto&`, `referans`ın referans
olduğunu unutmadı. `auto` ile `auto&` arasındaki tek fark bir `&` işareti, ama sonuç
tersine dönüyor: biri kopya kurar, öteki kaynağın kendisine bağlanır.

#### const auto&: Kopyasız Salt Okuma

Bir kapsayıcıyı yalnız okumak için gezerken `auto` her elemanı kopyalar. Eleman büyükse
(bir `std::string`, bir kayıt) bu kopyalama gereksiz bir maliyettir. `const auto&`, kopya
almadan kaynağın kendisini salt okunur biçimde bağlar.

```cpp
// const-auto-referans.cpp — buyuk elemani kopyalamadan okumak
#include <cstdio>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> stok{"elma", "armut", "kiraz"};

    for (const auto& ad : stok) {
        std::printf("%s\n", ad.c_str());
    }

    std::printf("eleman sayisi %zu\n", stok.size());
    return 0;
}
```

```text
elma
armut
kiraz
eleman sayisi 3
```

Döngü boyunca hiçbir `std::string` kopyalanmadı; `ad` her adımda `stok`un o anki
elemanına bağlandı. `const` derleyiciye bu bağlantı üzerinden yazma yapılmayacağını
söylüyor, `stok` döngü sonunda **aynen** kalıyor. Kopya almamak burada bir performans
kararı, kaynağa etkisi değişmiyor: `const auto&` de, kopyalayan `auto` da salt okuma
sırasında kaynağı bozmaz — fark, ara kopyanın var olup olmamasında.

#### auto&&: İletme Referansı

`auto&&` yazıldığında çıkarılan tip başlatıcının değer kategorisine göre değişir: bir sol
değerle başlatılırsa sıradan bir referansa (`T&`), bir sağ değerle başlatılırsa bir sağ
değer referansına (`T&&`) döner. Bu ikili davranışı gösteren `auto&&` biçimine **iletme
referansı** denir.

```cpp
// auto-iletme-referansi.cpp — auto&& deger kategorisine gore farkli tip cikarir
#include <cstdio>
#include <string>

int main() {
    std::string ad = "elma";

    auto&& sol_deger_baglantisi = ad;                    // string&
    auto&& sag_deger_baglantisi = std::string("armut");  // string&&

    sol_deger_baglantisi += " (guncellendi)";

    std::printf("ad: %s\n", ad.c_str());
    std::printf("sag deger: %s\n", sag_deger_baglantisi.c_str());
    return 0;
}
```

```text
ad: elma (guncellendi)
sag deger: armut
```

`sol_deger_baglantisi`, `ad`ın kendisine bağlandığı için üzerine yapılan ekleme `ad`e
ulaşıyor. `sag_deger_baglantisi` ise geçici bir nesneye bağlandı ve onun ömrünü ifadenin
sonuna kadar uzattı. İletme referansı yalnız `auto&&` ya da bir şablon parametresi
`T&&` biçiminde çıkarımla ortaya çıkar; elle yazılmış `std::string&&` bu davranışı
göstermez, çünkü tipi zaten sabittir, çıkarılmaz.

#### decltype ve decltype(auto)

`decltype`, `auto`dan farklı bir kurala uyar: bir ifadenin tipini, kaynağını
değiştirmeden **olduğu gibi** verir. `auto` referansı düşürürken `decltype` düşürmez.

```cpp
// decltype-ornek.cpp — decltype referansi dusurmez
#include <cstdio>

int main() {
    int deger = 10;
    int& referans = deger;

    decltype(deger) a = 1;        // int
    decltype(referans) b = deger; // int& -- referans dusmuyor

    b = 42;

    std::printf("deger %d, a %d\n", deger, a);
    return 0;
}
```

```text
deger 42, a 1
```

`b`, `decltype(referans)` sayesinde `int&` tipini aldı ve `deger`in kendisine bağlandı;
`a` ise sıradan bir `int` kopyası. `auto` burada ikisini de `int`e indirgerdi.

`decltype(auto)`, bir işlevin dönüş tipini `decltype` kurallarıyla çıkarmak için
kullanılır — ama bir tuzak taşır: dönüş ifadesinin **parantez içine alınması**, çıkarılan
tipi değiştirir. Aşağıdaki iki işlev aynı değişkeni döndürüyor gibi görünür, ama biri
değer, öteki referans çıkarır.

```cpp
// decltype-auto-parantez.cpp — parantez ifadeyi sol degere cevirip tipi degistirir
#include <cstdio>
#include <type_traits>

int sayac = 0;

decltype(auto) artir_ve_don() {
    ++sayac;
    return sayac;     // decltype(auto) -> int (degisken adi tek basina)
}

decltype(auto) artir_ve_don_parantezli() {
    ++sayac;
    return (sayac);   // decltype(auto) -> int& (parantezli ifade bir sol deger)
}

int main() {
    constexpr bool ilki_deger = std::is_same_v<decltype(artir_ve_don()), int>;
    constexpr bool ikincisi_referans =
        std::is_same_v<decltype(artir_ve_don_parantezli()), int&>;

    std::printf("ilki deger mi: %s\n", ilki_deger ? "evet" : "hayir");
    std::printf("ikincisi referans mi: %s\n", ikincisi_referans ? "evet" : "hayir");
    return 0;
}
```

```text
ilki deger mi: evet
ikincisi referans mi: evet
```

`decltype(auto)`, `return sayac;` için tek başına bir değişken adı görüp `int` çıkarır.
`return (sayac);` için ise parantezli ifadeyi bir sol değer olarak okur ve `int&` çıkarır.
İki işlev de burada `sayac`'ın kendisi genel kapsamda tanımlı olduğu için güvenle
çalışıyor; ama işlev içinde tanımlı **yerel** bir değişkeni aynı şekilde parantezle
döndürmek, işlev bittiğinde yok olan bir nesneye referans üretir — çağıran taraf bu
referansı okumadan önce bile tanımsız davranışa girilmiş olur. Bu yüzden `decltype(auto)`
dönüşünde parantez, göz ardı edilebilecek bir yazım tercihi değildir.

#### Yapısal Bağlama: Kopya mı, Referans mı

Yapısal bağlama bir kayıt tipini (bir `std::pair`, bir `std::map`'in elemanı) tek satırda
adlandırılmış parçalara ayırır: `auto [ad, adet] : stok`. Buradaki `auto` da aynı kuralı
izler — kaynağı kopyalar, referans istiyorsan `&` eklemen gerekir. Sırasız bir kapsayıcının
gezinme sırası tanımsız olduğundan aşağıdaki örnek `std::map` kullanıyor; anahtara göre
sıralı olduğu için çıktısı kararlıdır.

```cpp
// yapisal-baglama.cpp — kopya ile referansli yapisal baglamanin farki
#include <cstdio>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> stok{{"elma", 4}, {"armut", 2}};

    for (auto [ad, adet] : stok) {
        adet += 100;   // yalnizca kopyayi degistiriyor
    }

    std::printf("kopyadan sonra:");
    for (const auto& [ad, adet] : stok) {
        std::printf(" %s=%d", ad.c_str(), adet);
    }
    std::printf("\n");

    for (auto& [ad, adet] : stok) {
        adet += 100;   // kaynagin kendisini degistiriyor
    }

    std::printf("referanstan sonra:");
    for (const auto& [ad, adet] : stok) {
        std::printf(" %s=%d", ad.c_str(), adet);
    }
    std::printf("\n");
    return 0;
}
```

```text
kopyadan sonra: armut=2 elma=4
referanstan sonra: armut=102 elma=104
```

İlk döngü `auto [ad, adet]` ile her elemanın kopyasını bağladı; `adet += 100` yalnız o
kopyaya yazdı, `stok` **kopyadan sonra** satırında değişmeden görünüyor. İkinci döngü
`auto& [ad, adet]` ile elemanların kendisine bağlandı; aynı `+= 100` bu kez `stok`un
kendisine ulaştı. **KT40.** Yapısal bağlamada da tip çıkarımının sınıfı köşeli parantezden
önceki `&`e bağlıdır: `auto [...]` kaynağı **aynen** bırakır, `auto& [...]` kaynağı
**değişti**rir — ikisi de aynı kayıt tipini, aynı isimlerle parçalıyor, fark yalnız o tek
karakterde.

#### Okunabilirlik Dengesi

`auto` bir kısayoldur ama her yerde okunabilirliği artırmaz. Tip adı okura kaynağın ne
olduğunu, hangi işlemlerin geçerli olduğunu söylüyorsa yazılmalıdır: bir yineleyicinin
uzun şablon adı yerine `auto it` yazmak okuru rahatlatır, çünkü `it`in kendisi zaten bir
yineleyici olduğunu söylüyor. Ama bir işlevin dönüş tipi çağıran tarafın davranışını
belirliyorsa — örneğin dönen şey bir referans mı, bir kopya mı — bu bilgiyi `auto`nun
arkasına gizlemek okuru yanlış varsayıma iter. Bu dersin gösterdiği gibi, `auto` ile
`auto&` arasındaki fark derleyici için görünür bir `&` işaretidir, ama okuyucu için o
işareti fark etmek, koddaki kaynağın **aynen** mi kaldığını yoksa **değişti**ğini mi
kestirebilmesinin tek yoludur. Bu yüzden bir okuma değişkeninde `const auto&`, bir
elemanları değiştiren döngüde `auto&` yazmak; asıl tipin kendisi anlam taşıdığında da `auto`yu
atlayıp tipi olduğu gibi yazmak, kısayoldan ödün vermeden okunabilirliği korur.

M11/K04 (C++ Temelleri ve Nesne Tasarımı) bir ifadenin **hangi nesneyle** çalıştığını
ölçmüştü; oradaki `kopya` sınıfı bir **hedefi** adlandırıyordu — atamanın ya da yapıcının
yazdığı yeni nesneyi. Bu kursun `aynen` sınıfı ise bambaşka bir şeyi adlandırır: işlemin
**kaynağı**, yani zaten var olan, üzerinde çalışılan nesneyi. `for (auto x : v)` bir kopya
kurduğunda ortada tek bir işlem vardır; K04 o kopyanın hedefini sorardı, bu ders işlemin
kaynağa ne bıraktığını sorar. Aynı satır, iki farklı soruya iki farklı cevap verir.

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

Bu dersin ölçtüğü şey, kapsayıcı gezinmesinde çıkarılan tipin kaynağa ne bıraktığıdır.
Aynı vektörü iki farklı döngüyle geziyoruz; başlıktaki tek fark bir `&` işareti.

```cpp
// auto-referans-ile-yazma.cpp — auto& dongusu kaynagi degistiriyor mu
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> v{5, 7};
    auto once = v;

    for (auto& x : v) x *= 2;

    std::printf("auto& sonrasi:");
    for (int deger : v) std::printf(" %d", deger);
    std::printf("\n");
    std::printf("degisti mi: %s\n", v == once ? "hayir" : "evet");
    return 0;
}
```

```text
auto& sonrasi: 10 14
degisti mi: evet
```

```cpp
// auto-kopya-ile-okuma.cpp — auto kopya olusturuyor, kaynak degismiyor
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> v{5, 7};
    auto once = v;
    int toplam = 0;

    for (auto x : v) {
        toplam += x;
        x *= 2;   // yalnizca kopyayi degistiriyor, v'ye ulasmiyor
    }

    std::printf("auto kopya:");
    for (int deger : v) std::printf(" %d", deger);
    std::printf(", toplam %d\n", toplam);
    std::printf("degisti mi: %s\n", v == once ? "hayir" : "evet");
    return 0;
}
```

```text
auto kopya: 5 7, toplam 12
degisti mi: hayir
```

**KT41.** `for (auto& x : v) x *= 2;` her elemana kaynağın kendisi üzerinden bağlanır;
`x *= 2` doğrudan `v`ye yazar, döngü sonunda `v` **değişti**. `for (auto x : v)` ise her
adımda bir kopya kurar; `x *= 2` yalnız o kopyayı değiştirir, `v` döngü sonunda **aynen**
kalır — `toplam` kopyalardan hesaplanmış olsa bile kaynağa hiçbir şey yazılmadı. Tip
çıkarımının sınıfı burada başlıktaki tek karakterle belirleniyor: derleyici `&`i görüp
referans mı kopya mı kuracağına karar veriyor, okuyucunun bunu satırın geri kalanından
çıkarması gerekiyor. Bu dersin sınıfı, `auto&` biçiminin verdiği sonuçla adlandırılır:
**değişti**.

#### Özet

- Bu dersin merkezi ölçümünün sınıfı **değişti**: `for (auto& x : v) x *= 2;` kaynağın
  kendisine yazar, `for (auto x : v)` yalnız bir kopyayı değiştirir ve kaynağı **aynen**
  bırakır.
- `auto` bir ifadenin const'unu ve referansını düşürür; `auto&` referansı düşürmez,
  `const auto&` kopya almadan salt okur.
- `auto&&` bir iletme referansıdır: başlatıcı sol değerse `T&`, sağ değerse `T&&` çıkarır;
  bu davranış yalnız çıkarım yapılan bağlamlarda görülür.
- `decltype` bir ifadenin tipini olduğu gibi verir, referansı düşürmez; `decltype(auto)`
  bir işlevin dönüş tipini aynı kuralla çıkarır ve dönüş ifadesinin parantez içine
  alınması sonucu değere değil referansa çevirebilir.
- Yapısal bağlamada `auto [ad, adet]` kaynağı **aynen** bırakır, `auto& [ad, adet]`
  kaynağı **değişti**rir — fark yine köşeli parantezden önceki tek `&` işaretidir.
- Tip adını yazmak, o ad okura kaynağın davranışı hakkında bir şey söylediğinde
  gerekçelidir; aksi halde `auto` kısayolu okunabilirliği bozmaz.

#### Kurs Kapanışı

Bu kurs boyunca on bir farklı işlemi aynı soruyla ölçtük: bir işlem kaynağa ne bırakıyor?
Aşağıdaki tablo her dersin ölçtüğü işlemi, kaynağa kalanı ve o dersin sınırlayıcı ölçümünü
özetliyor.

| Ders | Ölçülen işlem | Kaynağa kalan | Sınırlayıcı ölçüm |
| --- | --- | --- | --- |
| Akıllı İşaretçiler | `unique_ptr` taşındı | boş | `std::move` tek başına taşımaz; kaynak **aynen** kalır, taşıyan yapıcı ya da taşıyan atama çağrılmadıkça |
| Taşıma Semantiği | `std::string` taşındı | belirsiz | `const` nesnede `std::move` kopyalayan yapıcıya düşer, kaynak **aynen** kalır |
| Sıfır ve Beş Kuralı | Yıkıcılı sınıfta `std::move` | aynen | Aynı satır, sınıfın yazılışına göre iki farklı sınıfa düşer: **aynen** ya da **belirsiz** |
| Kopyalanamaz ve Taşınamaz Tipler | Kendi taşıyan yapıcısı | boş | `is_move_constructible_v<Kilit>` **hayır** — kopyalayan yapıcı silinmiş, taşıma hiç bildirilmemiş |
| Kapsayıcılar | `map`'te `operator[]` ile okuma | değişti | `const map`'te `operator[]` derlenmez; okumak için bile yazma izni ister |
| Yineleyiciler ve Geçerlilik | Silinen yineleyici | belirsiz | Aynı silme `std::list`'te yalnız silinen düğümün yineleyicisini geçersizleştirir, ötekiler **aynen** kalır |
| Algoritmalar | `std::sort` | değişti | `sort` ve `stable_sort` ikisi de **değişti**, ama eşit elemanların sırası yalnız birinde garanti |
| Sil–Kaldır Deyimi | `std::remove` sonrası kuyruk | belirsiz | C++20'nin serbest `std::erase`'i kuyruğa hiç dokunmadan doğrudan **değişti**ye götürür |
| Lambdalar ve Fonksiyon Nesneleri | `mutable` değerle yakalama | aynen | Aynı gövde üç farklı yakalamada üç sınıf verir: değer **aynen**, referans **değişti**, taşıma **belirsiz** |
| Aralıklar | Sol değerden görünüm | aynen | Sağ değerden kurulan görünüm taşıyan yapıcıyla kaynağı **boş**a çevirir |
| Tip Çıkarımı | `auto&` ile döngü | değişti | `auto` ile `auto&` arasındaki tek `&` kaynağı **aynen**den **değişti**ye çevirir |

**3 aynen · 3 değişti · 2 boş · 3 belirsiz; durumu söylenen 8, söylenmeyen 3.** Bu dağılım
tablodaki on bir dersin dağılımıdır ve kursun başında kurulan çekirdeğin dağılımıyla
birebir aynıdır: her işlem kaynağı ya olduğu gibi bırakır, ya değiştirir, ya sözleşmeyle
boşaltır, ya da değerini söylemeden bırakır ve ona ancak yeni bir değer verildikten sonra
bakılabilir.

Sıradaki kurs **Şablonlar ve Başarım**; şablonlar, eşzamanlılık ve başarım konularını
işliyor.

