std::initializer_list ve Uniform Initialization
C++'ta şöyle bir satır yazmışsındır:
std::vector<int> nums = {1, 2, 3, 4, 5};Peki bu nasıl çalışıyor? Süslü parantezlerin içindeki sayılar nasıl oluyor da bir vector'e dönüşüyor? Cevap: std::initializer_list sayesinde. Bu ders, C++11'in en kullanışlı ama aynı zamanda en kafa karıştıran özelliklerinden birini anlatıyor.
Hazırsan, süslü parantezlerin büyülü dünyasına dalalım.
std::initializer_list Nedir?
std::initializer_list<T>, C++11 ile gelen hafif bir sınıftır. Süslü parantezlerle ({}) yazılan değer listesini temsil eder. Bir read-only, geçici (temporary) array view'dur.
Analoji: Alışveriş Listesi 🛒
Markete giderken bir kağıda yazıyorsun: "süt, ekmek, yumurta, peynir". Bu kağıt, ürünlerin kendisi değil — sadece bir liste. Listeyi kasiyere veriyorsun, kasiyer listedeki ürünleri tarıyor. Ürünler rafta zaten var, kağıt sadece onlara işaret ediyor.
std::initializer_list de tam böyle. {1, 2, 3} yazdığında derleyici geçici bir dizi oluşturur ve initializer_list bu diziye sadece bir "pencere" açar. Kendi belleği yok, kopyalama yapmaz, sadece mevcut değerlere bakmanı sağlar.
Temel Özellikler
Hafif (lightweight): İçinde sadece bir pointer ve bir size tutar
Read-only: Elemanları değiştiremezsin
Geçici: Genellikle ifade bitince ömrü sona erer
`<initializer_list>` header'ı gerekir
#include <iostream>
#include <initializer_list>
void printAll(std::initializer_list<int> values) {
std::cout << "Eleman sayisi: " << values.size() << std::endl;
for (int v : values) {
std::cout << v << " ";
}
std::cout << std::endl;
}
int main() {
printAll({10, 20, 30, 40, 50});
printAll({7});
printAll({}); // Bos liste de olur!
return 0;
}Çıktı:
Eleman sayisi: 5
10 20 30 40 50
Eleman sayisi: 1
7
Eleman sayisi: 0
Fonksiyon parametresi olarak std::initializer_list<int> aldığımızda, çağıran taraf süslü parantezlerle istediği kadar değer gönderebilir. Çok esnek, çok temiz.
Kendi Sınıfına initializer_list Constructor Ekleme
std::vector bunu yapabiliyorsa, senin sınıfın da yapabilir. Tek gereken: std::initializer_list parametreli bir constructor yazmak.
Kod Örneği: IntArray Sınıfı
#include <iostream>
#include <initializer_list>
#include <algorithm>
class IntArray {
private:
int* data;
size_t length;
public:
// initializer_list constructor
IntArray(std::initializer_list<int> list)
: data(new int[list.size()]), length(list.size()) {
std::cout << "initializer_list constructor: "
<< length << " eleman" << std::endl;
std::copy(list.begin(), list.end(), data);
}
// Normal constructor (boyut belirterek)
explicit IntArray(size_t size)
: data(new int[size]()), length(size) {
std::cout << "Size constructor: " << length << " eleman" << std::endl;
}
~IntArray() {
delete[] data;
}
// Kopyalamayi simdilik devre disi birakalim
IntArray(const IntArray&) = delete;
IntArray& operator=(const IntArray&) = delete;
size_t size() const { return length; }
int& operator[](size_t index) { return data[index]; }
const int& operator[](size_t index) const { return data[index]; }
void print() const {
std::cout << "[";
for (size_t i = 0; i < length; ++i) {
if (i > 0) std::cout << ", ";
std::cout << data[i];
}
std::cout << "]" << std::endl;
}
};
int main() {
IntArray arr = {10, 20, 30, 40, 50}; // initializer_list constructor
arr.print();
IntArray empty = {}; // initializer_list constructor (bos)
empty.print();
IntArray sized(5); // Size constructor (explicit)
sized.print();
return 0;
}Çıktı:
initializer_list constructor: 5 eleman
[10, 20, 30, 40, 50]
initializer_list constructor: 0 eleman
[]
Size constructor: 5 eleman
[0, 0, 0, 0, 0]Artık sınıfını {1, 2, 3} şeklinde başlatabilirsin. Bu, API'ni çok daha doğal hale getiriyor.
⚠️ Tehlike Bölgesi: initializer_list Constructor HER ZAMAN Öncelikli!
C++'ın en kafa karıştıran köşe durumlarından birine hoş geldin. Eğer bir sınıfın hem initializer_list constructor'ı hem de başka constructor'ları varsa, süslü parantez {} kullanıldığında initializer_list her zaman öncelik alır.
vector'ün Meşhur Tuzağı
#include <iostream>
#include <vector>
int main() {
// Dikkat: Bu ikisi FARKLI!
std::vector<int> v1(5); // 5 elemanli vector: {0, 0, 0, 0, 0}
std::vector<int> v2{5}; // 1 elemanli vector: {5}
std::cout << "v1 (parentez): ";
for (int x : v1) std::cout << x << " ";
std::cout << " (boyut: " << v1.size() << ")" << std::endl;
std::cout << "v2 (suslu): ";
for (int x : v2) std::cout << x << " ";
std::cout << " (boyut: " << v2.size() << ")" << std::endl;
// Daha da kafa karistirici:
std::vector<int> v3(5, 3); // 5 elemanli, hepsi 3: {3, 3, 3, 3, 3}
std::vector<int> v4{5, 3}; // 2 elemanli: {5, 3}
std::cout << "v3 (5, 3): ";
for (int x : v3) std::cout << x << " ";
std::cout << " (boyut: " << v3.size() << ")" << std::endl;
std::cout << "v4 {5, 3}: ";
for (int x : v4) std::cout << x << " ";
std::cout << " (boyut: " << v4.size() << ")" << std::endl;
return 0;
}Çıktı:
v1 (parentez): 0 0 0 0 0 (boyut: 5)
v2 (suslu): 5 (boyut: 1)
v3 (5, 3): 3 3 3 3 3 (boyut: 5)
v4 {5, 3}: 5 3 (boyut: 2)Gördün mü? vector<int>{5} yazdığında 5 elemanlı bir vector oluşturmuyorsun — tek elemanı 5 olan bir vector oluşturuyorsun! Çünkü {5} süslü parantez olduğu için initializer_list<int> constructor'ı öncelik alıyor.
Öncelik Kuralı
Derleyici süslü parantez {} gördüğünde şu sırayla bakar:
`initializer_list` constructor var mı? → Varsa ve elemanlar uyumlu türdeyse → ONU KULLAN
Yoksa → Normal constructor overload çözümlemesi yap
Bu kural her zaman geçerli. Hatta bazen çok garip sonuçlar doğurabilir:
#include <iostream>
#include <initializer_list>
class Tricky {
public:
Tricky(int a, int b) {
std::cout << "Normal: a=" << a << ", b=" << b << std::endl;
}
Tricky(std::initializer_list<int> list) {
std::cout << "initializer_list: " << list.size()
<< " eleman" << std::endl;
}
};
int main() {
Tricky t1(10, 20); // Normal constructor
Tricky t2{10, 20}; // initializer_list constructor! (sasirtici mi?)
Tricky t3 = {10, 20}; // initializer_list constructor
return 0;
}Çıktı:
Normal: a=10, b=20
initializer_list: 2 eleman
initializer_list: 2 elemanTricky{10, 20} yazsan bile, initializer_list constructor var olduğu sürece o çağrılıyor. Normal constructor'ı çağırmak istiyorsan () kullanmalısın.
⚠️ Dikkat: Kendi sınıfına
initializer_listconstructor eklerken çok dikkatli ol. Mevcut{}kullanan tüm kodun davranışı değişebilir. Bu yüzdeninitializer_listconstructor'ını sınıfın tasarımının başından planlaman önemli.
Brace Initialization ({}) vs Parentheses (()) Farkları
C++11 "uniform initialization" (tekdüze başlatma) olarak {} söz dizimini tanıttı. Amaç: her yerde aynı söz dizimini kullanmak. Ama "uniform" sözcüğüne rağmen {} ve () arasında önemli farklar var.
Fark 1: Narrowing Conversion Yasağı
Bu {}'nin en güzel özelliği:
int main() {
// Parentez ile: narrowing conversion'a IZIN VERIR
int a(3.14); // OK! a = 3 (kesme, veri kaybi)
int b(1e20); // OK! Taşma, tanımsız davranış
// Suslu parantez ile: narrowing conversion YASAK
// int c{3.14}; // HATA! double -> int narrowing
// int d{1e20}; // HATA! deger int'e sigmaz
int e{42}; // OK: 42 int'e sigar
double f{42}; // OK: int -> double narrowing degil (genisleme)
return 0;
}Narrowing conversion, veri kaybına yol açan dönüşümdür (double → int, long → short gibi). {} bunu derleme zamanında yakalar. Bu çok değerli bir güvenlik ağı.
Fark 2: initializer_list Önceliği
Yukarıda detaylıca gördük. {} kullanıldığında initializer_list constructor varsa o tercih edilir.
Fark 3: Most Vexing Parse Sorunu
C++'ın meşhur "most vexing parse" problemi, () ile tanımlama yapıldığında ortaya çıkar:
#include <iostream>
class Timer {
public:
Timer() { std::cout << "Timer olusturuldu!" << std::endl; }
};
int main() {
Timer t1(); // Bu bir FONKSIYON DEKLARASYONU! Timer nesnesi degil!
Timer t2{}; // Bu bir Timer nesnesi. Dogru!
Timer t3; // Bu da Timer nesnesi. Dogru!
// t1 icin derleyici uyari verebilir ama hata vermez
// Cunku t1, "Timer donduren, parametre almayan fonksiyon" olarak yorumlanir
return 0;
}{} kullanmak bu tür parsing belirsizliklerini ortadan kaldırır.
Fark 4: Aggregate Initialization
struct Point {
double x;
double y;
};
int main() {
Point p1{3.0, 4.0}; // OK: aggregate initialization
// Point p2(3.0, 4.0); // C++17 oncesi HATA! C++20'de OK.
return 0;
}Ne Zaman Hangisini Kullan?
| Durum | Tercih | Neden |
|---|---|---|
| Değişken tanımlama | {} | Narrowing koruması |
| vector boyut belirtme | () | initializer_list tuzağından kaçınma |
| Aggregate initialization | {} | Tek seçenek (C++20 öncesi) |
| initializer_list niyetli | {} | Doğal sözdizimi |
| auto ile kullanım | () veya = | auto x{5} → int (C++17) |
initializer_list'in İç Yapısı
std::initializer_list aslında çok basit bir yapıdadır. Kabaca şöyle düşünebilirsin:
// Kavramsal gosterim (gercek implementasyon derleyiciye bagli)
template<typename T>
class initializer_list {
private:
const T* begin_;
size_t size_;
public:
size_t size() const { return size_; }
const T* begin() const { return begin_; }
const T* end() const { return begin_ + size_; }
};Gördüğün gibi: sadece bir pointer ve bir boyut. Kopyalama yapmaz, sadece derleyicinin oluşturduğu geçici diziye işaret eder.
Yaşam Süresi Uyarısı
#include <initializer_list>
std::initializer_list<int> getList() {
return {1, 2, 3}; // TEHLIKE! Gecici dizi fonksiyon bitince yok edilir!
}
int main() {
auto list = getList(); // Dangling reference! Tanimsiz davranis!
for (int x : list) { // BOOM!
// ...
}
return 0;
}initializer_list'i fonksiyondan döndürmek tehlikelidir çünkü arkasındaki geçici dizi fonksiyon bitince yok edilir. initializer_list ise hâlâ o belleğe işaret eder.
💡 İpucu:
initializer_list'i fonksiyondan döndürme, fonksiyona parametre olarak geçir. Parametrede kullanım güvenlidir çünkü geçici dizi fonksiyon çağrısı boyunca yaşar.
Range-Based For ile Kullanım
std::initializer_list, begin() ve end() fonksiyonlarına sahip olduğu için range-based for loop ile doğrudan kullanılabilir:
#include <iostream>
#include <initializer_list>
#include <string>
class Logger {
private:
std::string prefix;
public:
Logger(const std::string& p) : prefix(p) {}
void logAll(std::initializer_list<std::string> messages) {
for (const auto& msg : messages) {
std::cout << "[" << prefix << "] " << msg << std::endl;
}
}
};
int main() {
Logger logger("APP");
logger.logAll({"Uygulama basladi", "Veritabani baglantisi kuruldu",
"Sunucu dinlemeye basladi", "Hazir!"});
return 0;
}Çıktı:
[APP] Uygulama basladi
[APP] Veritabani baglantisi kuruldu
[APP] Sunucu dinlemeye basladi
[APP] Hazir!Fonksiyona değişken sayıda argüman geçirmek için çok temiz bir yol.
initializer_list vs Variadic Templates
Değişken sayıda parametre almak için iki yöntem var: initializer_list ve variadic templates. Hangisini ne zaman kullanmalı?
initializer_list
// Tum elemanlar AYNI tur
void sum(std::initializer_list<int> values) {
int total = 0;
for (int v : values) total += v;
std::cout << "Toplam: " << total << std::endl;
}
int main() {
sum({1, 2, 3, 4, 5});
return 0;
}Variadic Templates
// Elemanlar FARKLI turler olabilir
template<typename... Args>
void printAll(Args... args) {
((std::cout << args << " "), ...); // C++17 fold expression
std::cout << std::endl;
}
int main() {
printAll(1, "merhaba", 3.14, 'x'); // Farkli turler!
return 0;
}Karşılaştırma Tablosu
| Özellik | initializer_list | Variadic Templates |
|---|---|---|
| Tür kısıtlaması | Tüm elemanlar aynı tür | Farklı türler olabilir |
| Eleman sayısı | Çalışma zamanında belli | Derleme zamanında belli |
| Performans | Geçici dizi oluşturulur | Inline genişletme, dizi yok |
| Kullanım kolaylığı | {1, 2, 3} — çok doğal | func(1, 2, 3) — parantez |
| Derleme zamanı | Hızlı | Yavaşlayabilir (template) |
| Erişim | Range-based for | Rekürsiyon veya fold expression |
Kural: Tüm elemanlar aynı türdeyse → initializer_list. Farklı türler varsa veya derleme zamanı optimizasyon gerekiyorsa → variadic templates.
Pratik Örnek 1: Custom Container
#include <iostream>
#include <initializer_list>
#include <algorithm>
template<typename T>
class SimpleVector {
private:
T* data;
size_t capacity;
size_t count;
void grow() {
capacity = capacity == 0 ? 4 : capacity * 2;
T* newData = new T[capacity];
std::copy(data, data + count, newData);
delete[] data;
data = newData;
}
public:
SimpleVector() : data(nullptr), capacity(0), count(0) {}
// initializer_list constructor
SimpleVector(std::initializer_list<T> list)
: data(new T[list.size()]), capacity(list.size()), count(list.size()) {
std::copy(list.begin(), list.end(), data);
}
~SimpleVector() { delete[] data; }
void push_back(const T& value) {
if (count >= capacity) grow();
data[count++] = value;
}
size_t size() const { return count; }
T& operator[](size_t i) { return data[i]; }
const T& operator[](size_t i) const { return data[i]; }
// Range-based for destegi
T* begin() { return data; }
T* end() { return data + count; }
const T* begin() const { return data; }
const T* end() const { return data + count; }
void print() const {
std::cout << "{";
for (size_t i = 0; i < count; ++i) {
if (i > 0) std::cout << ", ";
std::cout << data[i];
}
std::cout << "}" << std::endl;
}
};
int main() {
// initializer_list ile olusturma
SimpleVector<int> nums = {10, 20, 30, 40, 50};
nums.print();
// Sonradan eleman ekleme
nums.push_back(60);
nums.push_back(70);
nums.print();
// Range-based for
std::cout << "Elemanlar: ";
for (int n : nums) {
std::cout << n << " ";
}
std::cout << std::endl;
// String ile de calisir
SimpleVector<std::string> words = {"merhaba", "dunya", "cpp"};
words.print();
return 0;
}Kendi container sınıfına initializer_list constructor eklemek, kullanıcı deneyimini (developer experience) dramatik şekilde iyileştirir.
Pratik Örnek 2: Config Builder
#include <iostream>
#include <initializer_list>
#include <string>
#include <map>
#include <utility>
class Config {
private:
std::map<std::string, std::string> settings;
public:
// pair'lerden olusan initializer_list
Config(std::initializer_list<std::pair<std::string, std::string>> items) {
for (const auto& [key, value] : items) {
settings[key] = value;
}
}
std::string get(const std::string& key, const std::string& defaultVal = "") const {
auto it = settings.find(key);
return it != settings.end() ? it->second : defaultVal;
}
void print() const {
std::cout << "=== Config ===" << std::endl;
for (const auto& [key, value] : settings) {
std::cout << " " << key << " = " << value << std::endl;
}
}
};
int main() {
Config appConfig = {
{"host", "localhost"},
{"port", "8080"},
{"database", "myapp_db"},
{"log_level", "debug"},
{"max_connections", "100"}
};
appConfig.print();
std::cout << "\nPort: " << appConfig.get("port") << std::endl;
std::cout << "Timeout: " << appConfig.get("timeout", "30") << std::endl;
return 0;
}Çıktı:
=== Config ===
database = myapp_db
host = localhost
log_level = debug
max_connections = 100
port = 8080
Port: 8080
Timeout: 30Config nesnelerini süslü parantezlerle tanımlamak hem okunabilir hem de güçlü. JSON-benzeri bir söz dizimi elde ediyorsun.
Pratik Örnek 3: Matrix Initialization
#include <iostream>
#include <initializer_list>
#include <vector>
#include <iomanip>
class Matrix {
private:
std::vector<std::vector<double>> data;
size_t rows, cols;
public:
// Ic ice initializer_list ile matris olusturma
Matrix(std::initializer_list<std::initializer_list<double>> init) {
rows = init.size();
cols = 0;
for (const auto& row : init) {
data.emplace_back(row);
if (row.size() > cols) cols = row.size();
}
// Satirlari ayni boyuta getir (eksikleri 0 ile doldur)
for (auto& row : data) {
row.resize(cols, 0.0);
}
}
size_t getRows() const { return rows; }
size_t getCols() const { return cols; }
double& at(size_t r, size_t c) { return data[r][c]; }
const double& at(size_t r, size_t c) const { return data[r][c]; }
void print() const {
for (size_t r = 0; r < rows; ++r) {
std::cout << "| ";
for (size_t c = 0; c < cols; ++c) {
std::cout << std::setw(6) << std::fixed
<< std::setprecision(1) << data[r][c] << " ";
}
std::cout << "|" << std::endl;
}
}
// Matris toplama
Matrix operator+(const Matrix& other) const {
Matrix result = *this; // Kopya
for (size_t r = 0; r < rows; ++r) {
for (size_t c = 0; c < cols; ++c) {
result.data[r][c] += other.data[r][c];
}
}
return result;
}
};
int main() {
// Cok temiz bir sozdizimi!
Matrix A = {
{1.0, 2.0, 3.0},
{4.0, 5.0, 6.0},
{7.0, 8.0, 9.0}
};
Matrix B = {
{9.0, 8.0, 7.0},
{6.0, 5.0, 4.0},
{3.0, 2.0, 1.0}
};
std::cout << "Matris A:" << std::endl;
A.print();
std::cout << "\nMatris B:" << std::endl;
B.print();
Matrix C = A + B;
std::cout << "\nA + B:" << std::endl;
C.print();
return 0;
}Çıktı:
Matris A:
| 1.0 2.0 3.0 |
| 4.0 5.0 6.0 |
| 7.0 8.0 9.0 |
Matris B:
| 9.0 8.0 7.0 |
| 6.0 5.0 4.0 |
| 3.0 2.0 1.0 |
A + B:
| 10.0 10.0 10.0 |
| 10.0 10.0 10.0 |
| 10.0 10.0 10.0 |İç içe initializer_list kullanarak 2D veri yapılarını çok doğal bir söz dizimiyle oluşturabilirsin. Bu, matematiksel kütüphaneler için harika bir API sağlar.
Sık Yapılan Hatalar ve İpuçları
Hata 1: auto ile initializer_list
auto x1 = {1, 2, 3}; // x1'in turu: std::initializer_list<int>
auto x2{42}; // C++17: x2'nin turu int (C++11'de initializer_list<int> idi!)
auto x3 = {42}; // x3'un turu: std::initializer_list<int>auto ile {} kullanırken dikkatli ol. auto x{42} ve auto x = {42} farklı türler üretir (C++17'de).
Hata 2: initializer_list'i Üye Olarak Tutmak
class Bad {
std::initializer_list<int> data; // TEHLIKE! Dangling reference!
public:
Bad(std::initializer_list<int> list) : data(list) {}
// list'in arkasindaki gecici dizi constructor bitince yok edilir
// data artik gecersiz belleğe isaret ediyor
};initializer_list'i sınıf üyesi olarak tutma. Hemen vector veya array'e kopyala.
Hata 3: Boş Süslü Parantez Belirsizliği
class Widget {
public:
Widget() { std::cout << "Default" << std::endl; }
Widget(std::initializer_list<int> list) {
std::cout << "init_list: " << list.size() << std::endl;
}
};
int main() {
Widget w1{}; // Default constructor! (bos init_list degil)
Widget w2({}); // initializer_list constructor (bos liste)
}Boş {} her zaman default constructor'ı çağırır, boş initializer_list constructor'ını değil. Bu bir istisna kuralı.
Özet
std::initializer_list, süslü parantez
{}ile yazılan değer listelerini temsil eden hafif, read-only bir yapıdır. Kendi sınıfına kolayca eklenebilir.initializer_list constructor her zaman önceliklidir!
vector<int>{5}tek elemanlı vector oluştururkenvector<int>(5)beş elemanlı vector oluşturur. Bu farkı unutma.Brace initialization (`{}`) narrowing conversion'ı engeller —
int x{3.14}derlenmez. Bu güvenlik özelliği()'de yoktur.Container boyutu belirtirken `()`, eleman listesi verirken `{}` kullan. Bu convention kafa karışıklığını önler.
initializer_list'i fonksiyondan döndürme, sınıf üyesi olarak tutma. Arkasındaki geçici dizi yaşam süresini aşabilir.Tüm elemanlar aynı türdeyse
initializer_list, farklı türler gerekiyorsa variadic templates tercih et.
AI Asistan
Sorularını yanıtlamaya hazır