---
title: 'Şablon Özelleştirme'
source: 'https://academia.sh/tr/kurslar/cpp-sablonlar/sablon-ozellestirme'
course: 'Şablonlar ve Başarım'
language: tr
updated: '2026-10-21T05:02:46+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Tam özelleştirme ile kısmi özelleştirmeyi genel şablondan ayırt eder.'
    - 'Birden çok özelleştirme uyduğunda en özel olanın seçildiğini gösterir.'
    - 'Paket<bool> özelleştirmesinin yuva sayısını işaretçi eşitliğiyle ölçer.'
    - 'Bitten okumanın bedelini bellekten koşuma geri döndüren adımla açıklar.'
---

# Şablon Özelleştirme

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

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

## Genel Şablon ve Tam Özelleştirme

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

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

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

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

#endif
```

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

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

#include "paket.hpp"

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

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

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

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

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

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

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

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

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

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

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

#endif
```

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

#include "cift.hpp"

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

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

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

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

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

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

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

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

#endif
```

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

#include "isaretci.hpp"

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

```text
deger 7
isaretci 7
```

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

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

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

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

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

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

#endif
```

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

#include "etiket.hpp"

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

```text
diger tamsayi diger
```

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#include "paket.hpp"

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

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

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

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

## Özet

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

## Sonraki Adım

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