← Kursa Dön
📄 Text · 18 min

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:

  1. `initializer_list` constructor var mı? → Varsa ve elemanlar uyumlu türdeyse → ONU KULLAN

  2. 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 eleman

Tricky{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_list constructor eklerken çok dikkatli ol. Mevcut {} kullanan tüm kodun davranışı değişebilir. Bu yüzden initializer_list constructor'ı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?

DurumTercihNeden
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

Özellikinitializer_listVariadic Templates
Tür kısıtlamasıTüm elemanlar aynı türFarklı türler olabilir
Eleman sayısıÇalışma zamanında belliDerleme zamanında belli
PerformansGeçici dizi oluşturulurInline genişletme, dizi yok
Kullanım kolaylığı{1, 2, 3} — çok doğalfunc(1, 2, 3) — parantez
Derleme zamanıHızlıYavaşlayabilir (template)
ErişimRange-based forRekü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: 30

Config 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ştururken vector<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.