Ders 01 / 12
Fonksiyon ve Sınıf Şablonları
İşlev şablonu, şablon argüman çıkarımı, açık şablon argümanı ve sınıf şablonu; her yeni tip için derleyicinin yeni bir gövde üretmesi ve bu bedelin derlemeye taşınması.
Yayın Güncelleme
Bu derste
- İşlev şablonunun her yeni tip için ayrı bir gövde doğurduğunu somutlaştırma sayısıyla gösterir.
- Şablon argüman çıkarımı ile açık şablon argümanını ayırt eder.
- Sınıf şablonunda yalnız kullanılan üyelerin somutlaştırıldığını açıklar.
- Bedelin dört sınıftan hangisine düştüğünü derleme ve koşum ayrımıyla söyler.
İçindekiler
Önceki kurs, auto ile tip çıkarırken işlemin kaynağa ne bıraktığını sordu. Bu kurs daha
geniş bir şeyi soruyor: soyutlama bedava değildir, bedel yalnız yer değiştirir. Bir
şablon koşumdaki tip kararını kaldırır, ama her yeni tip için derleyiciye yeni bir gövde
yazdırır. Bir kilit veri yarışını kaldırır, ama iş parçacıklarını sıraya sokar. Kurs her
düzeneğe tek bir soru yöneltir: bu düzenek bedeli nereye taşıdı?
Dört Sınıf
Kursun her ölçümü bir sınıfa düşer ve bu dört ad kurs boyunca değişmez.
| Sınıf | Bedel nerede ödenir |
|---|---|
| derleme | Derleyicinin işidir: koşumda karar adımı kalmaz, derleme bir ürün bırakır (üretilen gövde, seçilen aday) |
| koşum | Her çağrıda ya da her nesnede yeniden ödenir: sayılan adım ya da ayırma sıfırdan büyüktür |
| bellek | Verinin bellekteki düzenindedir: aynı iş, farklı sayıda depolama yuvasına dokunur |
| bekleme | İş parçacıkları arasındaki eşgüdümdedir: her eşgüdüm noktası biri ötekini bekleyebilir |
Sınıflar bir üst ayrımda ikiye toplanır. Derleme sınıfında bedel program koşmadan önce ödenir; koşum, bellek ve bekleme sınıflarında program koşarken ödenir. Süre bu kursta ölçülmez ve basılmaz; sayılan şey adım, gövde, yuva, geçiş ve eşgüdüm noktasıdır.
Bu soru M11/K01’deki soruyla karışmasın. K01 bir kararı kimin verdiğini sordu; burada
soru, bir düzeneğin bedelini kimin ödediği ve o bedelin neyle sayıldığıdır. K01’in derleyici sınıfı bir kararı adlandırırdı;
bu kursun derleme sınıfı bir bedeli adlandırır ve yanında daima bir ürün sayısı taşır.
Ölçüm Çekirdeği
Çekirdek üç dosyadır. bedel.hpp dört sınıfı bir enum class olarak tanımlar ve iki işlev
bildirir; bedel.cpp her ölçümü tek satır olarak basar, sınıf sayacını artırır ve özeti
verir.
// bedel.hpp — bir duzenegin bedelini nerede odedigini adlandiran cekirdek
#ifndef BEDEL_HPP
#define BEDEL_HPP
#include <string>
enum class Bedel { derleme, kosum, bellek, bekleme };
// Olcum adini, sinifi ve gozlemi tek satirda basar, sinif sayacini artirir.
void raporla(const std::string& olcum, Bedel b, const std::string& gozlem);
// Sinif sayilarini ve ust ayrimi basar.
void ozet();
#endif
// bedel.cpp — siniflama, satir bicimi ve sayac
#include "bedel.hpp"
#include <cstdio>
namespace {
int sayac[4] = {0, 0, 0, 0};
const char* const adlar[4] = {"derleme", "kosum", "bellek", "bekleme"};
}
void raporla(const std::string& olcum, Bedel b, const std::string& gozlem) {
int i = static_cast<int>(b);
++sayac[i];
std::printf("%-26s %-8s %s\n", olcum.c_str(), adlar[i], gozlem.c_str());
}
void ozet() {
std::printf("siniflar: derleme %d, kosum %d, bellek %d, bekleme %d\n",
sayac[0], sayac[1], sayac[2], sayac[3]);
std::printf("derlemede odenen %d, kosumda odenen %d\n",
sayac[0], sayac[1] + sayac[2] + sayac[3]);
}
// tasindi.cpp — on iki duzenek, her biri icin tek soru: bedel nereye tasindi
#include <atomic>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>
#include "bedel.hpp"
static std::string say(const char* ad, long n) {
return std::string(ad) + "=" + std::to_string(n);
}
/* 1. Sablonlar: her yeni tip yeni bir govde dogurur */
static int govde = 0;
template <typename T>
T kare(T x) {
static const bool kayit = (++govde, true);
(void)kayit;
return x * x;
}
static void sablon() {
const int sabit = 4;
int cagri = 0;
kare(2); ++cagri;
kare(3); ++cagri;
kare(1.5); ++cagri;
kare(5L); ++cagri;
kare(sabit); ++cagri;
raporla("sablon somutlastirma", Bedel::derleme,
say("cagri", cagri) + " " + say("govde", govde));
}
/* 2. Ozellestirme: bool paketi sekiz degeri tek yuvada tutar */
template <typename T>
struct Paket {
T yuva[8] = {};
const void* yer(int i) const { return &yuva[i]; }
};
template <>
struct Paket<bool> {
unsigned char bitler = 0;
const void* yer(int) const { return &bitler; }
};
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;
}
static void ozellestirme() {
Paket<int> genel;
Paket<bool> bitli;
raporla("bool ozellestirmesi", Bedel::bellek,
say("int", ayri_yuva(genel)) + " " + say("bool", ayri_yuva(bitli)));
}
/* 3. Degisken sayida parametre: ozyineli acma her adimda govde dogurur */
static int oz_govde = 0;
static long topla_oz() {
static const bool kayit = (++oz_govde, true);
(void)kayit;
return 0;
}
template <typename Ilk, typename... Kalan>
long topla_oz(Ilk ilk, Kalan... kalan) {
static const bool kayit = (++oz_govde, true);
(void)kayit;
return ilk + topla_oz(kalan...);
}
static int kat_govde = 0;
template <typename... T>
long topla_kat(T... x) {
static const bool kayit = (++kat_govde, true);
(void)kayit;
return (0L + ... + x);
}
static void degisken() {
long a = topla_oz(1, 2, 3);
long b = topla_kat(1, 2, 3);
raporla("ozyineli paket acma", Bedel::derleme,
say("toplam", a == b ? a : -1) + " " + say("ozyineli", oz_govde) + " " +
say("katlama", kat_govde));
}
/* 4. Tip ozellikleri: secim derlemede yapilir, kosumda sorgu kalmaz */
static int sorgu = 0;
template <typename T>
const char* tur(T) {
if constexpr (std::is_integral_v<T>) return "tamsayi";
else return "kayan";
}
static const char* tur_etiketli(bool tamsayi_mi) {
++sorgu;
return tamsayi_mi ? "tamsayi" : "kayan";
}
static void tip_ozelligi() {
int ayni = 0;
ayni += std::string(tur(1)) == tur_etiketli(true);
ayni += std::string(tur(2L)) == tur_etiketli(true);
ayni += std::string(tur('a')) == tur_etiketli(true);
ayni += std::string(tur(1.0)) == tur_etiketli(false);
ayni += std::string(tur(1.0f)) == tur_etiketli(false);
ayni += std::string(tur(2u)) == tur_etiketli(true);
raporla("if constexpr ile secim", Bedel::derleme,
say("ayni", ayni) + " sorgu=0 " + say("etiketli", sorgu));
}
/* 5. SFINAE: yerine koyma basarisizligi hata degil, elemedir */
template <typename T, typename = void>
struct bolunur : std::false_type {};
template <typename T>
struct bolunur<T, std::void_t<decltype(std::declval<T>() / 2)>> : std::true_type {};
static const char* evet_hayir(bool b) { return b ? "evet" : "hayir"; }
static void sfinae() {
raporla("void_t ile algilama", Bedel::derleme,
std::string("int=") + evet_hayir(bolunur<int>::value) +
" string=" + evet_hayir(bolunur<std::string>::value));
}
/* 6. Statik cok bicimlilik: her turetilmis tip kendi govdesini alir */
static int crtp_govde = 0;
template <typename D>
struct Sekil {
long alan() const {
static const bool kayit = (++crtp_govde, true);
(void)kayit;
return static_cast<const D*>(this)->alan_hesapla();
}
};
struct Kare : Sekil<Kare> {
long kenar;
explicit Kare(long k) : kenar(k) {}
long alan_hesapla() const { return kenar * kenar; }
};
struct Dikdortgen : Sekil<Dikdortgen> {
long en, boy;
Dikdortgen(long e, long b) : en(e), boy(b) {}
long alan_hesapla() const { return en * boy; }
};
static void statik() {
long toplam = Kare(3).alan() + Dikdortgen(2, 5).alan() + Kare(4).alan();
raporla("crtp ile gonderim", Bedel::derleme,
say("toplam", toplam) + " " + say("govde", crtp_govde));
}
/* 7. Is parcaciklari ve kilitler: her artis bir kilit alir */
static void kilitler() {
std::mutex m;
long deger = 0, alma = 0;
std::vector<std::thread> isler;
for (int i = 0; i < 4; ++i)
isler.emplace_back([&] {
for (int j = 0; j < 10000; ++j) {
std::lock_guard<std::mutex> g(m);
++deger;
++alma;
}
});
for (auto& t : isler) t.join();
raporla("kilitli sayac", Bedel::bekleme, say("alma", alma) + " " + say("deger", deger));
}
/* 8. Bellek modeli: tek release yazisi bin duz yaziyi yayinlar */
static void bellek_modeli() {
std::vector<long> veri(1000);
std::atomic<bool> hazir{false};
int esgudum = 0;
std::thread uretici([&] {
for (int i = 0; i < 1000; ++i) veri[i] = i;
hazir.store(true, std::memory_order_release);
++esgudum;
});
while (!hazir.load(std::memory_order_acquire)) std::this_thread::yield();
long toplam = 0;
for (long x : veri) toplam += x;
uretici.join();
raporla("release/acquire yayini", Bedel::bekleme,
say("esgudum", esgudum) + " " + say("toplam", toplam));
}
/* 9. Onbellek dostu duzen: ayni alanlar yan yana mi duruyor */
static int atlama(const float* const* erisim, int n) {
int sayi = 0;
for (int i = 1; i < n; ++i)
if (erisim[i] != erisim[i - 1] + 1) ++sayi;
return sayi;
}
static void veri_duzeni() {
float aos[12] = {}; // x y z x y z ...
float soa[12] = {}; // x x x x y y y y z z z z
const float* aos_x[4];
const float* soa_x[4];
for (int i = 0; i < 4; ++i) {
aos_x[i] = &aos[i * 3];
soa_x[i] = &soa[i];
}
raporla("yalniz x alani", Bedel::bellek,
"aos=" + std::to_string(atlama(aos_x, 4)) + "/3 soa=" +
std::to_string(atlama(soa_x, 4)) + "/3");
}
/* 10. Profilleme: aracli sayaclar sicak noktayi gosterir */
static void profil() {
long hazirla = 0, ara = 0;
std::vector<int> v;
for (int i = 0; i < 100; ++i) {
v.push_back(i * 7 % 50);
++hazirla;
}
int ikiz = 0;
for (int i = 0; i < 100; ++i)
for (int j = i + 1; j < 100; ++j) {
++ara;
if (v[i] == v[j]) ++ikiz;
}
raporla("aracli profil", Bedel::kosum,
say("hazirla", hazirla) + " " + say("ara", ara) + " " + say("ikiz", ikiz));
}
/* 11. Derleme suresi: gizleme deyimi her nesnede bir ayirma yapar */
static int ayirma = 0;
class Pencere {
public:
explicit Pencere(int no);
~Pencere();
int no() const;
private:
struct Ic;
std::unique_ptr<Ic> ic_;
};
struct Pencere::Ic {
int no;
explicit Ic(int n) : no(n) { ++ayirma; }
};
Pencere::Pencere(int no) : ic_(std::make_unique<Ic>(no)) {}
Pencere::~Pencere() = default;
int Pencere::no() const { return ic_->no; }
static void gizleme() {
Pencere a(1), b(2), c(3);
raporla("gizleme deyimi", Bedel::kosum,
say("nesne", (a.no() > 0) + (b.no() > 0) + (c.no() > 0)) + " " + say("ayirma", ayirma));
}
/* 12. Paket yonetimi ve derleme araclari: degisen baslik kac birimi yeniden derletir */
struct Birim {
const char* ad;
std::vector<std::string> basliklar;
};
static int yeniden(const std::vector<Birim>& birimler, const std::string& degisen) {
int sayi = 0;
for (const auto& b : birimler)
for (const auto& h : b.basliklar)
if (h == degisen) {
++sayi;
break;
}
return sayi;
}
static void derleme_araci() {
std::vector<Birim> birimler = {
{"giris.cpp", {"ortak.hpp"}},
{"hesap.cpp", {"ortak.hpp"}},
{"rapor.cpp", {"ortak.hpp", "ozel.hpp"}},
};
raporla("degisen baslik", Bedel::derleme,
say("ortak", yeniden(birimler, "ortak.hpp")) + " " +
say("ozel", yeniden(birimler, "ozel.hpp")));
}
int main() {
sablon();
ozellestirme();
degisken();
tip_ozelligi();
sfinae();
statik();
kilitler();
bellek_modeli();
veri_duzeni();
profil();
gizleme();
derleme_araci();
ozet();
}
tasindi.cpp on iki düzeneği kurar; bu dersin düzeneği en üsttedir, ötekiler sonraki
derslerde açılır. Üç dosya birlikte derlenip koşturulunca şu çıktı gelir.
sablon somutlastirma derleme cagri=5 govde=3 bool ozellestirmesi bellek int=8 bool=1 ozyineli paket acma derleme toplam=6 ozyineli=4 katlama=1 if constexpr ile secim derleme ayni=6 sorgu=0 etiketli=6 void_t ile algilama derleme int=evet string=hayir crtp ile gonderim derleme toplam=35 govde=2 kilitli sayac bekleme alma=40000 deger=40000 release/acquire yayini bekleme esgudum=1 toplam=499500 yalniz x alani bellek aos=3/3 soa=0/3 aracli profil kosum hazirla=100 ara=4950 ikiz=50 gizleme deyimi kosum nesne=3 ayirma=3 degisen baslik derleme ortak=3 ozel=1 siniflar: derleme 6, kosum 2, bellek 2, bekleme 2 derlemede odenen 6, kosumda odenen 6
MP1. On iki düzeneğin altısı bedeli derleme sınıfına, ikisi koşum, ikisi
bellek, ikisi bekleme sınıfına taşıdı; derlemede ödenen altı, koşumda ödenen
altıdır. Çekirdek yalnız bu derste bütün olarak görünür;
sonraki derslerde yalnız ilgili kesiti alınır ve her ders kendi ölçümünü sade C++ ile
kurar: yerel static bir sayaç, bir adım sayacı ya da işaretçi eşitliği.
İşlev Şablonu ve Şablon Argüman Çıkarımı
kare bir işlev şablonudur: T bir şablon parametresidir ve gövde T bilinmeden
yazılır. Çağrı sırasında derleyici argümanın tipine bakıp Tyi çıkarır; buna şablon
argüman çıkarımı denir. Çıkarılan tip için gövde daha önce üretilmediyse derleyici onu üretir; bu üretime
somutlaştırma denir. Kayıt, gövdenin içindeki yerel static değişkenle tutulur: her
somutlaştırmanın kendi static’i vardır ve ilk çağrıda bir kez artar.
// kare.hpp — baslikta tanimlanan islev sablonu, govdeleri sayar
#ifndef KARE_HPP
#define KARE_HPP
inline int kare_govde = 0;
template <typename T>
T kare(T x) {
static const bool kayit = (++kare_govde, true);
(void)kayit;
return x * x;
}
#endif
Argümanı derleyiciye bırakmak zorunda değiliz: kare<long>(3) ile T açıkça verilir,
çıkarım yapılmaz ve argüman longa dönüştürülür. Aynı fikir bir sınıf şablonunda da
geçerlidir.
// kutu.hpp — sinif sablonu: her T icin ayri bir sinif
#ifndef KUTU_HPP
#define KUTU_HPP
inline int kutu_govde = 0;
template <typename T>
class Kutu {
public:
explicit Kutu(T deger) : deger_(deger) {}
T al() const {
static const bool kayit = (++kutu_govde, true);
(void)kayit;
return deger_;
}
private:
T deger_;
};
#endif
// somutlastirma.cpp — cikarim, acik argumanlar ve sinif sablonu govdeleri
#include <cstdio>
#include "kare.hpp"
#include "kutu.hpp"
int main() {
int a = kare(2); // T = int cikarildi
double b = kare(1.5); // T = double cikarildi
long c = kare<long>(3); // T acikca long verildi
double d = kare<double>(3); // 3 double'a donusur, double govdesi yeniden kullanilir
Kutu<int> iki(2);
Kutu<int> uc(3);
Kutu<double> yarim(0.5);
std::printf("kare %d %.2f %ld %.1f\n", a, b, c, d);
std::printf("kare govde %d\n", kare_govde);
std::printf("kutu %d %d %.1f\n", iki.al(), uc.al(), yarim.al());
std::printf("kutu govde %d\n", kutu_govde);
return 0;
}
kare 4 2.25 9 9.0 kare govde 3 kutu 2 3 0.5 kutu govde 2
MP2. kare<double>(3) yeni gövde doğurmadı, çünkü double gövdesi kare(1.5) ile zaten vardı.
Kutu<int> ile Kutu<double> iki ayrı sınıftır; iki Kutu<int> nesnesi tek al gövdesini
paylaştı. Sınıf şablonunda yalnız kullanılan üye işlevler somutlaştırılır.
MP3. Gövde, çağrı sayısına göre değil kullanılan tip sayısına göre üretilir: dört kare
çağrısı üç, üç Kutu::al çağrısı iki gövde doğurdu. Bedel derleme sınıfına taşındı;
koşumda tip kararı kalmadı, ödenen şey her yeni tip için derleyicinin yazdığı ek koddur.
Şablon Neden Başlıkta Tanımlanır
İki şablonun da tanımı .hpp içinde, bildirimle birlikte duruyor.
MP4. Somutlaştırma,
şablonun tanımının çağrının bulunduğu çeviri biriminde görünür olmasını gerektirir:
derleyici kare(2) için int gövdesini ancak tanımı o birimde görürse üretebilir. Tanım yalnız bir .cpp dosyasında
dursa, başka birimden gelen çağrı onu göremez ve gövde üretilemez. Bu yüzden tanım, onu
kullanan her çeviri biriminde görünen başlığa konur. Bunun da bir bedeli var; kursun son
dersleri ona döner: başlığı içeren her birim yeniden derlenir.
Sınırlayıcı Ölçüm
Gövde sayısını çağrı sayısı ya da argümanın yazılışı değil, çıkarılan tip belirler.
Bunu const taşıyan bir argümanla sınıyoruz.
// sabit-arguman.cpp — const int argumani yeni govde dogurmaz
#include <cstdio>
#include "kare.hpp"
int main() {
const int sabit = 4;
int a = kare(sabit); // T = int, const cikarimda dusur
int b = kare<int>(sabit); // T acikca int
int c = kare(2); // T = int
std::printf("kare %d %d %d\n", a, b, c);
std::printf("govde %d\n", kare_govde);
return 0;
}
kare 16 16 4 govde 1
MP5. kare(sabit) çağrısında argümanın const’u şablon argüman çıkarımı sırasında
düşer ve T int olur; çağrı, kare<int> gövdesini kullanır ve yeni gövde doğurmaz.
Üç çağrı, tek gövde; çekirdekteki cagri=5 govde=3 satırı bu yüzden beş çağrıya üç gövde
der. Sınıf derleme kalır, çünkü sayılan şey derlemenin bıraktığı gövdedir.
Özet
- Bu dersin ölçtüğü düzenek bedeli derleme sınıfına taşır: şablon, kullanılan her yeni tip için derleyiciye ayrı bir gövde yazdırır.
- Dört sınıf vardır: derleme, koşum, bellek, bekleme; çekirdekte altı düzenek derlemede, altısı koşumda ödenir.
- Şablon argüman çıkarımı
Tyi argümandan bulur; açık şablon argümanı çıkarımı atlar. - Gövde sayısını çıkarılan tip belirler:
const intargümanıconst’unu bırakır ve yeni gövde doğurmaz. - Sınıf şablonunda yalnız kullanılan üyeler somutlaştırılır; şablonun tanımı, onu kullanan her çeviri biriminde görünür olmalıdır.
Sonraki Adım
Bu derste şablon her yeni tip için bir gövde üretti ve bedel derlemeye taşındı. Sıradaki
ders, genel şablonu belirli bir tip için elle değiştirmenin bedeli nereye koyduğunu
soruyor: Paket<bool> sekiz değeri tek yuvaya sığdırınca bedel belleğin düzenine, oradan
da koşuma geçiyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.