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:
Bir
const char*pointer — karakter dizisinin başlangıcınaBir
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
| Özellik | std::string | std::string_view |
|---|---|---|
| Sahiplik (ownership) | ✅ Verilere sahip | ❌ Sadece bakıyor |
| Bellek ayırma | Heap allocation var | Yok — sıfır allocation |
| Kopyalama maliyeti | O(n) — tüm karakterler | O(1) — sadece pointer + size |
| Değiştirilebilir mi? | Evet (push_back, += vb.) | Hayır — read-only |
| Null-terminated garantisi | Evet | Hayır! |
sizeof | ~32 byte (+ heap) | ~16 byte |
| Ömür yönetimi | Kendi yönetir | Sen 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ıyorBu 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"; // MerhabaBu 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* pointersubstr — 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"; // MerhabaBu 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 behaviorstd::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_viewkullanı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şıyorNe 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
| Senaryo | const string& | string_view | Kazanan |
|---|---|---|---|
std::string argüman | Referans bağlama — ucuz | Pointer + size kopyası — ucuz | Berabere |
| C-string literal argüman | Geçici string oluşturur — pahalı | Doğrudan bağlanır — ucuz | string_view ✅ |
char* argüman | Geçici string oluşturur — pahalı | Doğrudan bağlanır — ucuz | string_view ✅ |
string_view argüman | Geçici string oluşturur — pahalı | Doğrudan kopyalanır — ucuz | string_view ✅ |
| Null-terminated gerekli mi? | Evet — c_str() mevcut | Hayır — garanti yok | Duruma bağlı |
.c_str() erişimi | ✅ str.c_str() | ❌ Yok | const string& |
| Substr işlemi | O(n) — yeni string | O(1) — yeni view | string_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'danstring'e dönüşüm explicit (açık) değildir — doğrudanstd::stringconstructor'ı 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()fonksiyonuconst char*döner ama bu pointer null-terminated olmayabilir. C API'larına geçirirken her zamanstd::string'e dönüştür veya%.*sformat 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"); // truecontains (C++23)
std::string_view text = "C++ is awesome";
text.contains("awesome"); // true — C++23
// C++20'de: text.find("awesome") != std::string_view::nposstd::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şturulabilirYaygı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'teHata 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üvenliKullanım Kılavuzu — Hızlı Referans
| Kullanım | Tercih | Neden |
|---|---|---|
| Fonksiyon parametresi (read-only) | string_view | Sıfır allocation, universal |
| Sınıf üyesi | string | Ownership gerekli |
| Return value (statik/literal) | string_view | Güvenli ve ucuz |
| Return value (dinamik) | string | Dangling riski |
| Map/set key | string | Ownership gerekli |
| Geçici depolama (parsing) | string_view | Hız, sıfır kopya |
| Kalıcı depolama | string | Güvenlik |
| C API'ya geçirme | string (.c_str()) | Null-termination gerekli |
| constexpr string | string_view | Derleme 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&yerinestring_viewkullan — C-string'ler için gereksiz heap allocation'ı önler.Sınıf üyesi olarak
string_viewkullanma — ownership yoktur, dangling reference riski yüksektir.std::stringtercih 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_suffixgibi 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 öncestd::string'e dönüştür.
AI Asistan
Sorularını yanıtlamaya hazır