---
title: 'Derleme Süresi Yönetimi'
source: 'https://academia.sh/tr/kurslar/cpp-sablonlar/derleme-suresi-yonetimi'
course: 'Şablonlar ve Başarım'
language: tr
updated: '2026-10-21T05:02:46+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'İleri bildirimin ve gereksiz include azaltmanın bir başlığı kaç çeviri birimine görünür kıldığını açıklar.'
    - 'Gizleme deyimiyle (pimpl) özel üyeleri bir ileri bildirimin arkasına saklar.'
    - 'Gizleme deyiminin yıkıcıyı ve taşıma işlemlerini neden .cpp dosyasında tanımlamayı gerektirdiğini açıklar.'
    - 'Derlemeden kazanılan bedelin koşuma nasıl taşındığını ayırma sayısıyla gösterir.'
---

# Derleme Süresi Yönetimi

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

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

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

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

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

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

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

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

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

class Pencere;

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

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

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

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

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

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

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

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

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

private:
    T deger_;
};

extern template class Kutu<int>;
```

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

template class Kutu<int>;
```

## Gizleme Deyimi

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Özet

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

## Sonraki Adım

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