---
title: 'Yineleyiciler ve Geçerlilik'
source: 'https://academia.sh/tr/kurslar/cpp-standart-kutuphane/yineleyiciler'
course: 'Kaynak Yönetimi ve Standart Kütüphane'
language: tr
updated: '2026-09-26T03:02:47+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'erase edilen bir elemana ait yineleyicinin neden bir daha okunamadığını gösterir'
    - 'Hangi kapsayıcı işlemlerinin yineleyicileri geçersizleştirdiğini, hangilerinin geçersizleştirmediğini ayırır'
    - 'Geçersizleşmeyen bir yineleyicinin işlemden sonra aynen kaldığını ölçer'
    - 'Yineleyici kategorileri arasındaki (girdi, ileri, çift yönlü, rastgele erişim) farkı tanır'
---

# Yineleyiciler ve Geçerlilik

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?
