---
title: 'Sil–Kaldır Deyimi'
source: 'https://academia.sh/tr/kurslar/cpp-standart-kutuphane/sil-kaldir-deyimi'
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:
    - "std::remove'un elemanları gerçekten silmediğini, yalnız öne taşıdığını gösterir"
    - "remove sonrası vector'ün kuyruğundaki değerlerin belirsiz olduğunu ölçer"
    - 'erase ile birlikte kullanıldığında kuyruğun gerçekten kaldırıldığını gösterir'
    - "C++20'nin std::erase serbest işlevinin aynı deyimi tek satıra indirdiğini tanır"
---

# Sil–Kaldır 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?
