C++ Dilinde std::string_view sınıf şablonu
- Yusuf Hançar
- Aug 4, 2024
- 10 min read
Updated: Aug 23, 2024
C++17 ile birlikte standartlara eklenen std::string_view, yazıların bellek içindeki farklı temsillerini gözlemleme ve üzerinde işlem yapma yeteneği sağlayan bir sınıf şablonudur(class template). std::string_view, adından da anlaşılacağı gibi, bir yazının bellek içindeki bir pencere görünümünü sağlar ve bu yazının fiili içeriğine dair herhangi bir değişiklik yapmadan onu incelememizi sağlamaktadır.
Bir yazı bellekte çeşitli şekillerde tutulabilir. En yaygın kullanım std::string sınıfıdır ki bu sınıf, yazıyı ardışık karakterler olarak depolar ve genellikle SSO (Small String Optimization) ile performansı artırır. Bununla birlikte, yazılar sadece 'std::string' ile değil, 'char str[20]' gibi statik diziler, 'std::vector<char>' gibi dinamik diziler veya 'std::array<char, 20>' gibi sabit boyutlu dizilerle de tutulabilir. Ayrıca, sabit bir metin literali olan "smartcode" gibi bir formatla da temsil edilebilir.
Ancak, yazıların bu çeşitli temsillerinden herhangi birine sahipse, bir yazıyı kopyalamak çoğu zaman ek bellek tahsisine yol açar. Bu tür kopyalama işlemlerinden kaçınmak ve belleği daha verimli kullanmak istenirse, std::string_view ideal bir çözüm olacaktır.
std::string_view, iki pointer tutar: biri yazının ilk karakterinin adresini, diğeri ise yazının son karakterinden sonraki adresi tutar. Bu yapısı sayesinde, yazının gerçek sahipliğinden bağımsız olarak, yazıya hızlı ve etkili bir şekilde erişmenize olanak tanır.
Bu yazımızda, std::string_view sınıfının detaylarına, avantajlarına ve nasıl kullanılacağına dair kapsamlı şekilde inceleyerek, hangi durumlarda kullanışlı olduğunu ve performans açısından neler sunduğunu gözlemleyeceğiz.
int main()
{
std::cout << "sizeof(char*) : " << sizeof(char*) << "\n";
std::cout << "sizeof(std::string_view) : " << sizeof(std::string_view) << "\n";
}
*****************************
AÇIKLAMA : string_view 2 poiter tutmaktadır
*****************************
CEVAP :
4
8
*****************************
std::string_view ile Performans ve Bellek Kullanımı
Bir std::string_view nesnesi, bir yazının ilk ve son karakterlerinin adreslerini tutan iki pointer içerir demiştik. Bu tasarım, bellek üzerindeki yazının gerçek içeriğine dair herhangi bir kopyalama yapmadan yazıya erişim sağlar. Ancak, iki pointer kullanmanın maliyetli olabileceği ve kontrolünü zorlaştırabileceği düşünülebilir.
void func(std::string_view str){}
int main()
{
func("smartcode");
}
string_view nesnesinin sahibi olunmayacağı için dikkatli kullanılmalıdır. Tanımsız davranış durumu olabilir ve dangling pointer incelenmelidir.
#include <iostream>
#include <string>
#include <string_view>
void view_func(std::string_view sv)
{
std::cout << sv << "\n";
}
int main()
{
std::string str = "SmartCode";
std::string_view sv = str; // std::string_view 'str' değişkenini gözlemler
view_func(sv); // SmartCode
// 'str' işlevden çıkarken bellekten serbest bırakılır ve 'sv' geçersiz hale gelir.
return 0;
}
*****************************
AÇIKLAMA : std::string_view str'ın karakterlerini gözlemler. Ancak, str işlevin bitiminde (main fonksiyonunun sonunda) ömrü sona erer ve bellekteki verisi serbest bırakılır. view hala str'ın eski adresini işaret etmeye devam eder, bu da tanımsız davranışa yol açar. Bu durumda, view kullanıldığında erişilen bellek geçersiz olacaktır.
*****************************
#include <iostream>
#include <string>
#include <string_view>
std::string_view view_get()
{
std::string str = "tempstr";
std::string_view sv = str; // Bu 'sv' geçici 'str' üzerinde çalışır
return sv; // 'str' yerel değişken olup işlevin bitiminde yok olur!!
}
int main()
{
std::string_view sv = view_get();
std::cout << sv << "\n"; // UB: 'sv' geçici 'str' üzerinde çalışır
return 0;
}
*****************************
AÇIKLAMA : view_get fonksiyonu geçici bir std::string nesnesi oluşturur ve bu nesneye bir std::string_view atar. Ancak, işlev bitiminde str nesnesinin ömrü sona erer ve sv geçersiz hale gelir. sv'nin bu geçersiz veriyi göstermesi, tanımsız davranışa neden olur.
*****************************
#include <iostream>
#include <string>
#include <string_view>
void view_func(std::string_view sv)
{
std::cout << sv << "\n";
}
int main()
{
std::string str = "tempstr";
std::string_view sv = str; // Geçerli 'str' üzerinde çalışan sv
view_func(sv); // Bu güvenlidir, çünkü 'str' ömrü boyunca 'sv' geçerlidir
// 'str' ömrü sona ermedikçe, 'sv' kullanılabilir.
return 0;
}
*****************************
AÇIKLAMA : sv 'str' üzerinde çalışır ve 'str' nesnesinin ömrü boyunca sv geçerli kalır ve güvenli bir kullanım sağlar.
*****************************
Diğer önemli nokta NTBS(null terminated byte string) olmak zorunda değildir. run-time error oluşabilir.
#include <iostream>
#include <string>
#include <string_view>
void view_func(std::string_view sv)
{
std::cout << sv << "\n"; // Bu, null karakteri olmayan verilerle tehlikeli olabilir
}
int main()
{
// Null-terminated bir C dizisi
const char* cstr = "smartcode";
std::string_view sv1(cstr);
view_func(sv1); // safe
// Null-terminated olmayan veri
char no_term[] = {'s', 'm', 'a', 'r', 't', 'c', 'o', 'd', 'e', '\0', '!', '\0'};
std::string_view sv2(no_term, 10); // '!' ve '\0' dahil değil
view_func(sv2); // run-time hatalarına yol açabilir
return 0;
}
*****************************
AÇIKLAMA : sv1 güvenlidir çünkü cstr null-terminated bir C dizisidir. Ancak, sv2'nin gözlemlediği dizi null-terminated değildir ve view_func işlevi bu veriyi null karakterine kadar okumaya çalışır. Bu durumda, işlev no_term dizisinin sonundaki ! karakterini de okur ve bellek sınırlarını aşabilir ve tanımsız davranışa yol açar.
*****************************
#include <iostream>
#include <string>
#include <string_view>
void view_func(std::string_view sv)
{
std::cout.write(sv.data(), sv.size()) << "\n";
}
int main()
{
// Null-terminated olmayan veri
char data[] = { 's', 'm', 'a', 'r', 't', 'c', 'o', 'd', 'e' };
std::string_view sv(data, sizeof(data)); // Uzunluğu doğru belirlenmiş
view_func(sv); // Güvenli şekilde veri yazdırılır
return 0;
}
*****************************
AÇIKLAMA : view_func işlevi std::string_view'in uzunluğunu size() metodu ile doğru bir şekilde belirler ve veri sınırları aşılmadan yazdırılır.
*****************************
string_view arayüzündeki tüm üye fonksiyonlar(istisnalar hariç) const üye fonksiyonlardır. Yazıyı salt okuma amaçlı kullanırlar.
size_t size() const noexcept;
bool empty() const noexcept;
const char* data() const noexcept;
const char& operator[](size_t pos) const;
const char& front() const;
const char& back() const;
std::string_view substr(size_t pos = 0, size_t count = npos) const;
size_t find(std::string_view sv, size_t pos = 0) const noexcept;
size_t rfind(std::string_view sv, size_t pos = npos) const noexcept;
string_view nesnesini construct etmenin ilk yolu default constructor...
int main()
{
string_view sv;
std::cout << boolalpha;
cout << "sv.size() : " << sv.size() << endl;
cout << "sv.length() : " << sv.length() << endl;
cout << "sv.empty() : " << sv.empty() << endl;
cout << (sv.data() == nullptr) << endl;
}
*****************************
AÇIKLAMA :
*****************************
CEVAP :
sv.size() : 0
sv.length() : 0
sv.empty() : true
true
*****************************
int main()
{
char str[] = "smart";
string_view sv1{ "code" };
string_view sv{ str };
}
*****************************
AÇIKLAMA : NTBS ile init edildi.
*****************************
#include <iostream>
#include <string_view>
int main()
{
char str[] = "smartcode";
std::string_view sv(str, 5);
std::cout << "sv : " << sv << "\n";
std::cout << "sv.data() : " << sv.data() << "\n";
}
*****************************
CEVAP :
sv : smart
sv.data() : smartcode
*****************************
#include <iostream>
#include <string_view>
#include <array>
int main()
{
std::array<char, 5> arr{ 'c', '+', '+', '1', '7' };
std::string_view sv(arr.data(), arr.size());
std::cout << "sv : " << sv << "\n";
std::cout << "sv.length() : " << sv.length() << "\n";
}
*****************************
AÇIKLAMA : legal
*****************************
CEVAP :
sv : c++17
sv.length() : 5
*****************************
#include <iostream>
#include <string_view>
#include <array>
int main()
{
std::array<char, 5> arr{ 'c', '+', '+', '1', '7' };
std::string_view sv(arr.data(), arr.size());
// ntbs ister ve tanımsız davranış olur.
std::cout << "sv.data() : " << sv.data() << "\n";
puts(sv.data()); // ub
}
*****************************
AÇIKLAMA : sv.data() tanımsız davranış olur. std::string_view C-String (null-terminated string) değildir.
std::string_view'in data() işlevi, veri dizisinin null-terminated olmadığını garanti etmez. puts işlevi bir C-String bekler ve null karakter ile biten bir dizeyi yazdırır. Eğer arr null karakter içermiyorsa, puts fonksiyonu veri bitimini belirleyemez ve bellek hatalarına yol açabilir.
*****************************
CEVAP : undefined behaviour
*****************************
#include <iostream>
#include <string_view>
int main()
{
std::string_view sv = "smart\0\0\0code";
std::cout << "sv : " << sv << "\n";
std::cout << "sv.data() : " << sv.data() << "\n";
std::cout << "sv.length() : " << sv.length() << "\n";
}
*****************************
AÇIKLAMA : "smart\0\0\0code" dizesi, C-string (null-terminated string) formatında tanımlanır. Burada dize smart ile başlar, ardından üç tane null karakter ('\0') ve son olarak code gelir. Bu dize, std::string_view'in geçerli olduğu bir C-string'dir, ancak std::string_view bu dizedeki null karakterleri dikkate almaz. sv'yi yazdırırken std::string_view'in içeriği sadece ilk null karaktere kadar olan kısmı gösterecektir çünkü std::cout null karakteri gördüğünde diziyi sonlandırır.
*****************************
CEVAP :
sv : smart
sv.data() : smart
sv.length() : 5
*****************************
#include <iostream>
#include <string_view>
int main()
{
const char* ptr = "smart\0\0\0code";
std::string_view sv(ptr, 12);
std::cout << "sv : " << sv << "\n";
std::cout << "sv.data() : " << sv.data() << "\n";
std::cout << "sv.length() : " << sv.length() << "\n";
}
*****************************
AÇIKLAMA : null karakterleri yazdırmadı.
*****************************
CEVAP :
sv : smartcode
sv.data() : smart
sv.length() : 12
*****************************
#include <iostream>
#include <string_view>
#include <string>
int main()
{
std::string str{ "smartcode" };
std::string_view sv{ str };
}
*****************************
AÇIKLAMA : str.operator std::basic_string_view<char, std::char_traits<char>>()
std::string_view sınıfının, std::string'den doğrudan bir parametre alabilen constructor'ı yoktur. Ancak, bu kullanım geçerlidir çünkü std::string ve std::string_view arasındaki dönüşüm genellikle bir std::string_view'in std::string'in data() ve size() metodlarından oluşturulabilmesini sağlar.
*****************************
int main()
{
std::string str{ "smartcode" };
// auto sv = str.operator std::basic_string_view<char, std::char_traits<char>>();
auto sv = str;
}
*****************************
AÇIKLAMA :
str.operator std::basic_string_view<char, std::char_traits<char>>() fonksiyonu explicit olmadığı için doğrudan str yazabiliyoruz.
*****************************
#include <iostream>
#include <string_view>
int main()
{
char name[] = "smartcode";
std::string_view sv{ name + 5, static_cast<size_t>(name + 10 - (name + 5)) };
std::cout << "sv.length() : " << sv.length() << "\n";
std::cout << "sv : " << sv << "\n";
return 0;
}
*****************************
AÇIKLAMA : const char* parametreli ctor var.
explicit string_view(const char* s);
*****************************
CEVAP :
sv.length() : 5
sv : code
*****************************
#include <iostream>
#include <string_view>
int main()
{
std::string str{ "smartcode" };
std::string_view sv(str.begin(), str.end());
// std::string_view sv{ str.data(), str.size() }; c++17 legal
}
*****************************
AÇIKLAMA : c++17 error çünkü range parametreli ctor yok.
string_view(const_iterator first, const_iterator last);
*****************************
CEVAP :
*****************************
#include <iostream>
#include <string_view>
int main()
{
char str[] = "C++14";
std::string_view sv(str);
std::cout << sv << "\n";
std::cout << sv.front() << "\n";
std::cout << sv.back() << "\n";
str[4] = '7';
std::cout << sv << "\n";
}
*****************************
AÇIKLAMA : yazı değişirse bizde değişmiş yazıyı gözlemlemiş oluruz.
*****************************
CEVAP :
C++14
C
4
C++17
*****************************
std::string_view nullptr parametreli ctor var ancak delete edilmiştir.
#include <iostream>
#include <string_view>
int main()
{
std::string_view sv = nullptr;
}
*****************************
AÇIKLAMA : delete fonksiyona çağrı
string_view(nullptr_t);
*****************************
CEVAP : syntax error
*****************************
std::string sınıfının std::string_view parametreli explicit constructor'ı vardır.
#include <iostream>
#include <string_view>
int main()
{
std::string_view sv{ "smartcode" };
std::string str{ sv };
}
*****************************
CEVAP : legal
*****************************
#include <iostream>
#include <string_view>
int main()
{
std::string_view sv{ "smartcode" };
std::string str = sv;
}
*****************************
AÇIKLAMA : ctor explicit olduğu için örtülü dönüşüme kapaldıır.
*****************************
CEVAP : syntax error
*****************************
#include <iostream>
#include <string_view>
void func(std::string str)
{
std::cout << str << "\n";
}
int main()
{
std::string_view sv{ "smartcode" };
func(sv);
}
*****************************
AÇIKLAMA : ctor explicit olduğu için örtülü dönüşüme kapaldıır.
*****************************
CEVAP : syntax error
*****************************
#include <iostream>
#include <string_view>
std::string func()
{
std::string_view sv{ "smartcode" };
return sv;
}
int main()
{}
*****************************
AÇIKLAMA : ctor explicit olduğu için örtülü dönüşüme kapaldıır.
*****************************
CEVAP : syntax error
*****************************
std::string_view data() fonksiyonu...
"constexpr const_pointer data() const noexcept;" Bu fonksiyon std::string_view nesnesinin gösterdiği karakter dizisinin başlangıç adresini (bir işaretçiyi) döndürür ve parametresi yoktur.
data() işaretçisinin geçerliliği, std::string_view'in gösterdiği veri kaynağının geçerliliğine bağlıdır. Eğer std::string_view'in gösterdiği string nesnesi değişirse veya yok olursa, data() fonksiyonunun döndürdüğü pointer geçersiz hale gelir.
std::string_view içeriğini std::ostream (örneğin, std::cout) ile uyumlu olduğu için, karakter dizisini yazdırabiliriz. std::string_view yazdırıldığında, arka planda std::ostream'in operator<< işlevi kullanılır.
std::string_view ve dangling pointer durumu
std::string foo()
{
return "smartcode";
}
int main()
{
auto str = foo();
}
std::string foo()
{
return "smartcode";
}
int main()
{
const string& str = foo();
cout << "str : " << str << "\n";
}
*****************************
AÇIKLAMA : life extension durumu
*****************************
CEVAP : legal
*****************************
std::string foo()
{
return "smartcode";
}
int main()
{
std::string&& str = foo();
std::cout << "str : " << str << "\n";
}
*****************************
AÇIKLAMA : life extension durumu
*****************************
CEVAP : legal
*****************************
std::string foo()
{
return "smartcode";
}
int main()
{
auto str = foo().c_str();
std::cout << "str : " << str << "\n";
}
*****************************
AÇIKLAMA : life extension durumu yoktur.
foo() fonksiyonu, bir std::string nesnesi döndürüyor ve bu nesne geçici bir nesnedir. Geçici nesne, fonksiyon çağrısının bitiminde yok edilir. Bu durumda, c_str() işlevinin döndürdüğü pointer geçici bir nesneye işaret eder ve bu nesne yok edildikten sonra bu pointer geçersiz olur.
*****************************
CEVAP : undefined behaviour
*****************************
Çözüm :
#include <iostream>
#include <string>
std::string foo()
{
return "smartcode";
}
int main()
{
std::string str = foo(); // Geçici nesne std::string değişkenine atanır
const char* cstr = str.c_str(); // geçerli std::string'in c_str() işlevi
std::cout << "str : " << cstr << "\n"; // Geçerli bir pointer
}
class Data {
private :
std::string m_data{};
public :
Data(const std::string& str) : m_data{ str } {}
void print() const
{
std::cout << m_data << "\n";
}
std::string_view get_data() const
{
return m_data;
}
};
Data create_data()
{
return Data{ "smartcode" };
}
int main()
{
auto sv = create_data().get_data();
std::cout << "sv : " << sv << "\n";
}
*****************************
AÇIKLAMA : dangling pointer hatası
std::string_view, std::string nesnesinin verilerini gösterir ve geçici Data nesnesinin yaşam süresi boyunca geçerlidir. Ancak, create_data() fonksiyonunun çağrısından sonra, geçici Data nesnesi yok edilir ve std::string_view'ın işaret ettiği std::string verileri geçersiz hale gelir.
*****************************
CEVAP : undefined behaviour
*****************************
int main()
{
std::string str(10, 'a');
std::string_view sv{ str };
str.append(15, 'b'); // reallocation
std::cout << "sv : " << sv << "\n";
}
*****************************
AÇIKLAMA : reallocation oldu. yazı başka bir yere taşındı ve sv içindeki pointer danglig oldu.
-> std::string dinamik olarak yönetilen bir dizedir ve içeriği değiştirildiğinde bellek yerleşimi değişebilir.
std::string_view, bir std::string'in alt kümesine işaret edebilir, ancak std::string'in içeriği değiştiğinde std::string_view'in içeriği geçersizleşebilir.
-> Başlangıçta, str'in bellekte ayrılan alanı 10 karakter uzunluğundadır.
str.append(15, 'b') işlemi, str'e 15 'b' karakteri ekler. Eğer str'in mevcut belleği yeterli değilse, std::string yeni bir bellek alanı ayırır ve eski içeriği bu yeni alana taşır. Bu durumda, str'in bellekteki adresi değişir.
-> std::string_view bir std::string'in içeriğini işaret eder. Eğer std::string'in içeriği taşınırsa (reallocation), std::string_view içindeki pointer dangling olur ve tanımsız davranışa yol açar.
!!! std::string içeriği değiştirildiğinde std::string_view yeniden oluşturulamalıdır. !!!
*****************************
CEVAP : undefined behaviour
*****************************
int main()
{
auto ptr = new std::string{ "smartcode" };
std::string_view sv{ *ptr };
std::cout << "sv : " << sv << "\n";
delete ptr;
std::cout << "sv : " << sv << "\n"; // ub
}
*****************************
AÇIKLAMA : pointer danglig olur.
*****************************
CEVAP : undefined behaviour
*****************************
template <typename T>
T func(const T& x, const T& y)
{
return x + y;
}
std::string operator+(std::string_view sv1, std::string_view sv2)
{
return std::string(sv1) + std::string(sv2);
}
int main()
{
std::string_view sv = "smartcode";
auto val = func(sv, sv);
std::cout << "val : " << val << "\n";
}
*****************************
AÇIKLAMA : parametreler x + y yani string+string yani string ancak func fonksiyonunun geri dönüş değeri string_view
geri dönüş string_view geçici nesne döndürdük, life extension olmadı
*****************************
CEVAP : undefined behaviour
*****************************
template <typename T>
auto func(const T& x, const T& y)
{
return x + y;
}
std::string operator+(std::string_view sv1, std::string_view sv2)
{
return std::string(sv1) + std::string(sv2);
}
int main()
{
std::string_view sv = "smartcode";
auto val = func(sv, sv);
std::cout << "val : " << val << "\n";
}
*****************************
AÇIKLAMA : geri dönüş değeri string olacak.
*****************************
CEVAP : legal
*****************************
template return type deduction için hatırlatma!
template <typename T>
auto foo(T val)
{
return val + val;
}
*****************************
AÇIKLAMA : auto return type
geri dönüş değeri val + val durumuna göre yapılacaktır.
*****************************
template <typename T>
auto foo(T val)->decltype(val + val)
{
return val + val;
}
*****************************
AÇIKLAMA : trailing return type(c++11)
geri dönüş değeri decltype çıkarımına göre yani -> sağ tarafındaki ifade ne ise ona göre yapılır yani return ifadesinden çıkarım yapılmaz.
*****************************
std::string_view, sınıfın veri elemanını initialize etmek için de tercih edilmektedir.
class Data {
public :
Data(const std::string& str) : ms{ str } {}
private :
std::string ms;
};
class Data {
public :
Data(std::string_view sv) : ms{ sv } {}
private :
std::string ms;
};
class Data {
public :
Data(std::string str) : ms{ std::move(str) } {}
private :
std::string ms;
};
input parameter | const string& | string_view | string and move |
const char* | 2 allocations | 1 allocations | 1 allocations + move |
const char* (SSO) | 2 copies | 1 copy | 2 copies |
l value | 1 allocation | 1 allocation | 1 allocation + 1 move |
l value (SSO) | 1 copy | 1 copy | 2 copies |
r value | 1 allocation | 1 allocation | 2 moves |
r value (SSO) | 1 copy | 1 copy | 2 copies |
C++20 ve c++23 ile eklenen bazı özellikler...
int main()
{
std::string_view sv{ "smartcode" };
std::cout << boolalpha << sv.starts_with("smart") << "\n"; // c++20
std::cout << sv.ends_with("ode") << "\n"; // c++20
std::cout << sv.contains("art"); // c++23
}
*****************************
AÇIKLAMA :
*****************************
CEVAP :
true
true
true
*****************************
Comments