← Kursa Dön
📄 Text · 18 min

std::string_view Kullanımı ve Best Practices

C++17 ile gelen std::string_view, string'lerle çalışma şeklimizi değiştiren küçük ama güçlü bir araç. Bir string'e "sahip olmadan" bakabilmeni sağlar — kopyalama yok, bellek ayırma yok, sadece okuma.

Bu ders, string_view'ı ne zaman kullanacağını, ne zaman kaçınacağını ve en önemlisi dangling reference tuzaklarından nasıl kurtulacağını anlatıyor.

Analoji: Vitrin Camından Bakmak

Bir mağazanın vitrinini düşün. Camın arkasındaki ürünleri görebilirsin, inceleyebilirsin, fiyatlarını okuyabilirsin. Ama o ürünler senin değil — sadece bakıyorsun.

`std::string_view` tam olarak bu. Bir karakter dizisine (string) sahip olmadan, sadece "bakma hakkı" veren hafif bir görüntüleyici. Vitrin camı kırılırsa (string yok edilirse), sen de artık bir şey göremezsin.

std::string bir evi satın almak gibidir — evin anahtarı sende, istediğini yaparsın. std::string_view ise o evi dışarıdan seyretmek gibidir — güzel görünür ama kapıyı açamazsın.

string_view Nedir?

std::string_view (<string_view> header'ında), bir karakter dizisi üzerindeki non-owning view'dir. İçinde sadece iki şey tutar:

  1. Bir const char* pointer — karakter dizisinin başlangıcına

  2. Bir size_t — uzunluk

Hepsi bu. Bellek ayırmaz, kopyalamaz, sahiplenmez. Sadece "şuradan şuraya kadar olan karakterlere bakıyorum" der.

#include <string_view>
#include <iostream>

int main() {
    std::string_view sv = "Merhaba Dünya";
    // sv içinde: pointer → "Merhaba Dünya" (string literal)
    //            length = 14

    std::cout << sv << "\n";           // Merhaba Dünya
    std::cout << sv.size() << "\n";    // 14
    std::cout << sv[0] << "\n";        // M
}

sizeof(std::string_view) genelde 16 byte'tır (64-bit sistemde: 8 byte pointer + 8 byte size). Oysa std::string tipik olarak 32 byte + heap allocation. Farkı hissediyor musun?

string vs string_view: Temel Farklar

Özellikstd::stringstd::string_view
Sahiplik (ownership)✅ Verilere sahip❌ Sadece bakıyor
Bellek ayırmaHeap allocation varYok — sıfır allocation
Kopyalama maliyetiO(n) — tüm karakterlerO(1) — sadece pointer + size
Değiştirilebilir mi?Evet (push_back, += vb.)Hayır — read-only
Null-terminated garantisiEvetHayır!
sizeof~32 byte (+ heap)~16 byte
Ömür yönetimiKendi yönetirSen yönetirsin — tehlikeli!

En kritik fark: ownership. string kendi verisine sahiptir, ne zaman isterse kopyalar, taşır, yok eder. string_view başkasının verisine bakar — o veri yok olursa string_view çöpe işaret eder.

Constructor'lar

string_view birçok şekilde oluşturulabilir:

String Literal'den

std::string_view sv1 = "merhaba";       // C-string literal'den
std::string_view sv2("dünya");           // Doğrudan constructor
std::string_view sv3 = "uzun metin"sv;   // C++17 literal suffix

"merhaba"sv notasyonu için using namespace std::string_view_literals; veya using namespace std::literals; gerekir.

std::string'den

std::string str = "C++ harika";
std::string_view sv = str;  // Implicit conversion — string → string_view
// sv, str'nin iç buffer'ına bakıyor

Bu dönüşüm implicit (örtük) ve ucuzdur — sadece pointer ve size kopyalanır. Ama dikkat: str değişirse veya yok edilirse, sv geçersiz olur!

Pointer + Uzunluk ile

const char* data = "Merhaba Dünya";
std::string_view sv(data, 7);  // Sadece "Merhaba" — ilk 7 karakter
std::cout << sv << "\n";       // Merhaba

Bu constructor özellikle binary data veya alt-string'lerle çalışırken kullanışlıdır.

Boş string_view

std::string_view empty;           // Boş — pointer = nullptr, size = 0
std::string_view empty2("");      // Boş — pointer = "", size = 0
std::cout << empty.empty();       // true

Üye Fonksiyonlar

string_view, std::string'in okuma fonksiyonlarının çoğunu destekler. Ama hiçbir yazma fonksiyonu yoktur.

Temel Erişim

std::string_view sv = "Merhaba Dünya";

sv.size();       // 14 (length() ile aynı)
sv.empty();      // false
sv[0];           // 'M' — bounds check yok
sv.at(0);        // 'M' — bounds check var, hata fırlatabilir
sv.front();      // 'M'
sv.back();       // 'a'
sv.data();       // const char* pointer

substr — Alt String Alma

string_view::substr yeni bir string_view döner — kopyalama yok, O(1):

std::string_view sv = "Merhaba Dünya";
std::string_view sub = sv.substr(8, 5);  // "Dünya"
// sub, aynı belleğe bakıyor — sadece offset ve uzunluk farklı

Bunu std::string::substr ile karşılaştır: o yeni bir string oluşturur, heap allocation yapar, karakterleri kopyalar. O(n) maliyet.

find — Arama

std::string_view sv = "C++ is powerful and fast";

auto pos = sv.find("powerful");   // 7
auto pos2 = sv.find('a');         // 14
auto pos3 = sv.find("Python");   // std::string_view::npos

if (pos != std::string_view::npos) {
    std::cout << "Bulundu: pozisyon " << pos << "\n";
}

rfind, find_first_of, find_last_of, find_first_not_of, find_last_not_of — hepsi mevcut.

remove_prefix ve remove_suffix — Daraltma

Bu iki fonksiyon, string_view'ı baştan veya sondan kısaltır. Orijinal veriyi değiştirmez — sadece pencereyi kaydırır.

std::string_view sv = "<<<Merhaba>>>";

sv.remove_prefix(3);   // sv artık "Merhaba>>>"
sv.remove_suffix(3);   // sv artık "Merhaba"

std::cout << sv << "\n"; // Merhaba

Bu işlemler O(1) — pointer'ı ileri kaydırma ve uzunluğu azaltma. Parsing ve tokenization için mükemmel.

starts_with ve ends_with (C++20)

std::string_view filename = "rapor_2024.pdf";

if (filename.starts_with("rapor")) {
    std::cout << "Bu bir rapor dosyası\n";
}

if (filename.ends_with(".pdf")) {
    std::cout << "PDF formatında\n";
}

C++20 öncesinde bunu elle yazmak gerekiyordu. Artık tek satır.

Dangling Reference TEHLİKESİ

Bu bölüm dersin en kritik kısmı. string_view kullanmanın en büyük riski dangling reference (sarkan referans) oluşturmaktır.

Tehlike 1: Fonksiyondan Dönen String'in View'ı

std::string_view dangerous() {
    std::string local = "geçici veri";
    return local;  // ❌ TEHLİKE — local yok edilecek!
}

int main() {
    std::string_view sv = dangerous();
    std::cout << sv << "\n";  // 💀 Undefined behavior!
    // sv, yok edilmiş bir string'in belleğine bakıyor
}

local fonksiyon bitince yok edilir. Ama sv hâlâ o belleğe bakıyor. Bu, yıkılmış bir binanın adresine mektup göndermek gibi — mektup ulaşmaz, belki başka bir yere gider.

Tehlike 2: Geçici String'den View Alma

std::string getName() {
    return "Ali Veli";
}

int main() {
    std::string_view sv = getName();  // ❌ TEHLİKE!
    // getName() geçici bir string döner
    // Bu satırın sonunda geçici string yok edilir
    // sv artık çöpe bakıyor

    std::cout << sv << "\n";  // 💀 Undefined behavior!
}

Bu çok sinsi bir hata. Derleyici uyarı bile vermeyebilir. Geçici string bu satırın sonunda yok olur, ama sv hâlâ ona işaret eder.

Tehlike 3: String Değişikliği Sonrası

std::string str = "Merhaba";
std::string_view sv = str;

str += " Dünya";  // ⚠️ string reallocation olabilir!
// str büyüdüğünde yeni bellek alanı alabilir
// sv hâlâ eski belleğe bakıyor olabilir

std::cout << sv << "\n";  // 💀 Potansiyel undefined behavior

std::string kapasitesini aştığında yeni bellek ayırır ve içeriği taşır. Eski bellek serbest bırakılır. Ama string_view hâlâ eski adrese bakıyor.

⚠️ Dikkat: string_view kullanırken altın kural: view'ın ömrü, baktığı verinin ömründen kısa olmalı. Bu kuralı ihlal eden her kod, undefined behavior riskine girer. Derleyici bunu her zaman yakalayamaz — sorumluluk sende.

Güvenli Kullanım Örnekleri

// ✅ Güvenli: string literal program boyunca yaşar
std::string_view sv1 = "kalıcı metin";

// ✅ Güvenli: aynı scope'ta, str sv'den önce yok edilmez
std::string str = "merhaba";
std::string_view sv2 = str;
std::cout << sv2;  // str hâlâ hayatta

// ✅ Güvenli: fonksiyon parametresi olarak — çağıran taraf sahipliği tutar
void process(std::string_view sv) {
    // sv, fonksiyon süresince geçerli
    std::cout << sv << "\n";
}
process(str);  // str fonksiyon dönene kadar yaşıyor

Ne Zaman string_view, Ne Zaman string?

Bu en pratik bölüm. Ezbere kurallar:

Fonksiyon Parametresi → string_view

// ❌ Eski yol
void log(const std::string& msg);

// ✅ Modern yol
void log(std::string_view msg);

Neden? const string& kullandığında, C-string ("merhaba") geçirirsen derleyici geçici bir string oluşturur — heap allocation. string_view ile bu maliyet sıfır.

void logMessage(std::string_view msg) {
    std::cout << "[LOG] " << msg << "\n";
}

logMessage("C-string literal");    // ✅ Sıfır allocation
logMessage(std::string("hello"));  // ✅ Implicit conversion
logMessage(someString);            // ✅ Ucuz — pointer + size kopyası

// const string& ile:
void logOld(const std::string& msg);
logOld("C-string literal");  // ❌ Geçici string oluşturulur — heap alloc!

Sınıf Üyesi → string ✅ (string_view ❌)

// ❌ Tehlikeli!
class User {
    std::string_view name;  // Kim sahip bu veriye?
public:
    User(std::string_view n) : name(n) {}
};

// Sorun:
User createUser() {
    std::string temp = "Ali";
    return User(temp);  // temp yok edilecek → name dangling!
}

// ✅ Güvenli
class User {
    std::string name;  // Kendi verisine sahip
public:
    User(std::string_view n) : name(n) {}  // Parametre sv, üye string
};

Sınıf üyeleri genelde nesnenin ömrü boyunca yaşamalıdır. string_view sahiplik vermez — dışarıdaki veri yok olursa üye çöpe bakar. Sınıf üyeleri için daima `std::string` kullan.

Return Value → Dikkatli Ol ⚠️

// ✅ Güvenli: string literal'e view dönmek
std::string_view getGreeting() {
    return "Merhaba";  // String literal program boyunca yaşar
}

// ❌ Tehlikeli: yerel string'e view dönmek
std::string_view getDynamic(int id) {
    std::string result = "User_" + std::to_string(id);
    return result;  // 💀 result yok edilecek!
}

// ✅ Güvenli alternatif: string dön
std::string getDynamic(int id) {
    return "User_" + std::to_string(id);  // Sahipliği çağırana ver
}

Kural basit: dönüş değeri olarak string_view ancak kalıcı veriye (string literal, static veri, parametre olarak gelen string) bakıyorsan güvenli.

const string& vs string_view Karşılaştırma

Senaryoconst string&string_viewKazanan
std::string argümanReferans bağlama — ucuzPointer + size kopyası — ucuzBerabere
C-string literal argümanGeçici string oluşturur — pahalıDoğrudan bağlanır — ucuzstring_view
char* argümanGeçici string oluşturur — pahalıDoğrudan bağlanır — ucuzstring_view
string_view argümanGeçici string oluşturur — pahalıDoğrudan kopyalanır — ucuzstring_view
Null-terminated gerekli mi?Evet — c_str() mevcutHayır — garanti yokDuruma bağlı
.c_str() erişimistr.c_str()❌ Yokconst string&
Substr işlemiO(n) — yeni stringO(1) — yeni viewstring_view

Sonuç: fonksiyon parametresi olarak string_view, çoğu durumda const string&'den daha iyidir. Ama c_str() gerekiyorsa (C API'larına geçirme) const string& veya string kullan.

💡 İpucu: string_view'dan string'e dönüşüm explicit (açık) değildir — doğrudan std::string constructor'ı ile yapabilirsin: ``cpp std::string_view sv = "merhaba"; std::string s(sv); // ✅ explicit construction std::string s2{sv}; // ✅ brace init std::string s3 = std::string(sv); // ✅ açık dönüşüm ` Bu dönüşüm heap allocation yapar çünkü string` artık sahipliği alıyor.

Pratik Örnek 1: Log Fonksiyonu

#include <string_view>
#include <iostream>
#include <chrono>

enum class LogLevel { INFO, WARN, ERROR };

std::string_view levelToString(LogLevel level) {
    switch (level) {
        case LogLevel::INFO:  return "INFO";   // String literal — güvenli
        case LogLevel::WARN:  return "WARN";
        case LogLevel::ERROR: return "ERROR";
    }
    return "UNKNOWN";
}

void log(LogLevel level, std::string_view message) {
    // string_view parametre: sıfır kopyalama!
    std::cout << "[" << levelToString(level) << "] "
              << message << "\n";
}

int main() {
    log(LogLevel::INFO, "Sistem başlatılıyor");    // C-string — 0 alloc
    log(LogLevel::WARN, std::string("Bellek azaldı")); // string — 0 ekstra alloc

    std::string detail = "Bağlantı hatası: port 8080";
    log(LogLevel::ERROR, detail);  // string → string_view — ucuz
}

levelToString fonksiyonu string_view döner — güvenli çünkü string literal'lere bakıyor. log fonksiyonu string_view parametre alır — C-string, string, veya başka bir string_view hepsini sıfır allocation ile kabul eder.

Pratik Örnek 2: Basit Token Parser

#include <string_view>
#include <vector>
#include <iostream>

std::vector<std::string_view> split(std::string_view input,
                                      char delimiter) {
    std::vector<std::string_view> tokens;

    while (!input.empty()) {
        // Delimiter'ı bul
        auto pos = input.find(delimiter);

        if (pos == std::string_view::npos) {
            // Son token
            tokens.push_back(input);
            break;
        }

        // Delimiter'dan önceki kısmı al
        tokens.push_back(input.substr(0, pos));
        // Pencereyi kaydır
        input.remove_prefix(pos + 1);
    }

    return tokens;
}

int main() {
    std::string data = "ali,veli,ayse,fatma";
    // data hayatta olduğu sürece token'lar geçerli!
    auto tokens = split(data, ',');

    for (auto token : tokens) {
        std::cout << "[" << token << "]\n";
    }
    // Çıktı: [ali] [veli] [ayse] [fatma]
}

Bu parser sıfır allocation ile çalışır (vector'ün kendi allocation'u hariç). Her token, orijinal string'in bir parçasına string_view olarak bakar. substr ve remove_prefix — ikisi de O(1).

Ama dikkat: tokens vektöründeki string_view'lar, data string'ine bağımlı. data yok edilirse, tüm token'lar dangling olur.

Pratik Örnek 3: Config Okuyucu

#include <string_view>
#include <string>
#include <map>
#include <iostream>
#include <sstream>

std::map<std::string, std::string> parseConfig(std::string_view content) {
    std::map<std::string, std::string> config;

    while (!content.empty()) {
        // Satır sonunu bul
        auto lineEnd = content.find('\n');
        std::string_view line = (lineEnd != std::string_view::npos)
            ? content.substr(0, lineEnd)
            : content;

        // Boşlukları temizle (basit trim)
        while (!line.empty() && line.front() == ' ')
            line.remove_prefix(1);
        while (!line.empty() && line.back() == ' ')
            line.remove_suffix(1);

        // Yorum veya boş satırı atla
        if (!line.empty() && line.front() != '#') {
            auto eq = line.find('=');
            if (eq != std::string_view::npos) {
                auto key = line.substr(0, eq);
                auto val = line.substr(eq + 1);

                // key ve value'dan boşlukları temizle
                while (!key.empty() && key.back() == ' ')
                    key.remove_suffix(1);
                while (!val.empty() && val.front() == ' ')
                    val.remove_prefix(1);

                // map'e string olarak sakla (ownership gerekli!)
                config[std::string(key)] = std::string(val);
            }
        }

        // Sonraki satıra geç
        if (lineEnd == std::string_view::npos) break;
        content.remove_prefix(lineEnd + 1);
    }

    return config;
}

int main() {
    std::string configFile = R"(
# Veritabanı ayarları
host = localhost
port = 5432
database = myapp
# Uygulama ayarları
debug = true
max_connections = 100
)";

    auto config = parseConfig(configFile);

    for (auto& [key, val] : config) {
        std::cout << key << " => " << val << "\n";
    }
}

Bu örnekte string_view parsing sırasında kullanılıyor — satırları bulmak, ayırmak, trim etmek. Ama sonuçları map'e saklarken std::string'e dönüştürüyoruz çünkü map sahipliğe ihtiyaç duyar.

Bu pattern çok yaygın: parse ederken `string_view`, saklarken `string`.

Null-Termination Tuzağı

std::string her zaman null-terminated'dır — c_str() fonksiyonu sondaki \0 karakterini garanti eder. Ama string_view için böyle bir garanti yok.

std::string str = "Merhaba";
std::string_view sv = str;
std::string_view sub = sv.substr(0, 3);  // "Mer"

// sub.data() "Merhaba" dizisinin başına işaret eder
// ama sub.size() = 3
// sub.data() null-terminated DEĞİL (kendi bağlamında)

printf("%s", sub.data());  // ❌ TEHLİKE — printf null'a kadar okur
                            // "Merhaba" yazdırabilir, çöp yazdırabilir

// Güvenli yol:
printf("%.*s", (int)sub.size(), sub.data());  // ✅
// veya string'e çevir:
std::string safe(sub);
printf("%s", safe.c_str());  // ✅

C API'ları (printf, fopen, vs.) null-terminated string bekler. string_view'ı doğrudan C API'larına geçirme — önce std::string'e çevir.

⚠️ Dikkat: string_view::data() fonksiyonu const char* döner ama bu pointer null-terminated olmayabilir. C API'larına geçirirken her zaman std::string'e dönüştür veya %.*s format specifier kullan.

Performance Karşılaştırma

Somut rakamlarla görelim:

#include <string>
#include <string_view>
#include <chrono>
#include <iostream>

// const string& versiyonu
size_t countVowels_str(const std::string& s) {
    size_t count = 0;
    for (char c : s)
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u')
            count++;
    return count;
}

// string_view versiyonu
size_t countVowels_sv(std::string_view s) {
    size_t count = 0;
    for (char c : s)
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u')
            count++;
    return count;
}

int main() {
    const char* cstr = "bu bir test cumlesidirrrrrrrrrr";

    // C-string ile çağırma
    countVowels_str(cstr);  // Geçici string oluşturulur — heap alloc!
    countVowels_sv(cstr);   // Doğrudan bağlanır — 0 alloc!

    // Döngüde milyonlarca kez çağırırsan fark belirginleşir
}

Fonksiyon gövdeleri aynı — fark çağrı sitesinde. C-string veya string_view geçirdiğinde const string& versiyonu geçici string oluşturmak zorunda. string_view versiyonu sıfır maliyet.

constexpr string_view

string_view constexpr-friendly'dir — derleme zamanında kullanılabilir:

constexpr std::string_view greeting = "Merhaba";
constexpr auto len = greeting.size();  // Derleme zamanında: 7

static_assert(greeting.size() == 7);
static_assert(greeting[0] == 'M');
static_assert(greeting.starts_with("Mer"));  // C++20

// Derleme zamanı string tablosu
constexpr std::string_view days[] = {
    "Pazartesi", "Sali", "Carsamba", "Persembe",
    "Cuma", "Cumartesi", "Pazar"
};
static_assert(days[0] == "Pazartesi");

std::string C++20'ye kadar constexpr değildi (ve hâlâ sınırlı). string_view ise C++17'den beri tam constexpr desteğine sahip.

C++20 ile Gelen İyileştirmeler

C++20, string_view'a birkaç güzel ekleme yaptı:

starts_with / ends_with

std::string_view path = "/api/v2/users";
path.starts_with("/api");    // true
path.ends_with("/users");    // true

contains (C++23)

std::string_view text = "C++ is awesome";
text.contains("awesome");    // true — C++23
// C++20'de: text.find("awesome") != std::string_view::npos

std::string'in string_view Kabul Eden Fonksiyonları

C++20 ile std::string sınıfına da starts_with, ends_with eklendi. Ayrıca string'in birçok fonksiyonu artık string_view parametresi kabul eder:

std::string s = "Merhaba Dünya";
s.starts_with("Mer");     // C++20 ✅
s.ends_with("Dünya");     // C++20 ✅

// String'in find, compare gibi fonksiyonları string_view alabilir
std::string_view target = "Dünya";
auto pos = s.find(target);  // ✅

Range Constructor (C++23 ile Genişletildi)

// C++23: ranges ile uyumlu constructor'lar
// std::string artık range'lerden oluşturulabilir

Yaygın Hatalar ve Çözümleri

Hata 1: string_view'ı Map Key Olarak Kullanmak

// ❌ Tehlikeli
std::map<std::string_view, int> cache;
std::string key = "user_" + std::to_string(id);
cache[key] = 42;
// key scope'tan çıkınca → cache'teki key dangling!

// ✅ Güvenli
std::map<std::string, int> cache;
cache[std::string(sv)] = 42;  // Sahiplik map'te

Hata 2: string_view'ı Üye Olarak Saklamak

// ❌
struct Config {
    std::string_view hostname;  // Kimin verisine bakıyor?
};

// ✅
struct Config {
    std::string hostname;  // Kendi verisine sahip
};

Hata 3: Geçici String'den Oluşturmak

// ❌
std::string_view sv = std::to_string(42);  // Geçici string yok edilir!

// ✅
std::string temp = std::to_string(42);
std::string_view sv = temp;  // temp yaşadığı sürece güvenli

Kullanım Kılavuzu — Hızlı Referans

KullanımTercihNeden
Fonksiyon parametresi (read-only)string_viewSıfır allocation, universal
Sınıf üyesistringOwnership gerekli
Return value (statik/literal)string_viewGüvenli ve ucuz
Return value (dinamik)stringDangling riski
Map/set keystringOwnership gerekli
Geçici depolama (parsing)string_viewHız, sıfır kopya
Kalıcı depolamastringGüvenlik
C API'ya geçirmestring (.c_str())Null-termination gerekli
constexpr stringstring_viewDerleme zamanı uyumu

Özet

  • `std::string_view` bir karakter dizisine sahip olmadan bakan, hafif (16 byte), read-only bir görüntüleyicidir. İçinde sadece pointer ve uzunluk tutar.

  • Fonksiyon parametresi olarak const string& yerine string_view kullan — C-string'ler için gereksiz heap allocation'ı önler.

  • Sınıf üyesi olarak string_view kullanma — ownership yoktur, dangling reference riski yüksektir. std::string tercih et.

  • Dangling reference en büyük tehlike: view'ın ömrü, baktığı verinin ömründen kısa olmalı. Geçici string'den view alma, fonksiyondan yerel string'in view'ını dönme — bunlar klasik tuzaklar.

  • `substr`, remove_prefix, remove_suffix gibi işlemler O(1) — yeni bir view dönerler, kopyalama yapmazlar. Parsing ve tokenization için ideal.

  • Null-terminated garantisi yoktur — C API'larına (printf, fopen) geçirmeden önce std::string'e dönüştür.