Haftalık C++ 23 – std::string_view

Evet, bir diğer yeni C++ kabiliyet yazımız ile birlikteyiz. Bir süredir farklı mecralarda gördüğüm ama bir türlü yazmak kısmet olmayan std::string_view sınfına bugün bir göz atacağız.

Bunu yaparken de öncelikle, bu sınıf öncesinde elimizde neler vardı, neden böyle bir sınıfa ihtiyacımız var, hangi durumlarda bunu kullanabiliriz ve hangi koşullarda kullanmak pek doğru değil gibi hususlara bu yazıda kısaca bakacağız.

Bu kabiliyet C++ 17 ile aramıza katıldı ve kullanmak için <string_view> başlık dosyasını eklemeniz gerekmektedir. Bu sınıftan önce, metin yönetimi ve karakter dizileri için std::string ve C den de bildiğimiz const char* dizilerimiz mevcuttu ve genel olarak kullanımlarına baktığımızda, aşağıdaki gibi fonksiyonlara geçirilip kullanılabilmekteydiler:

Aslına bakarsanız normal şartlarda bu kullanımların hiç bir sıkıntısı yok, ta ki farklı tipte veriler ile uğraşmak zorunda kaldığınızda, işin rengi değişmeye başlıyor:

  • Örneğin, C++ kodu içerisinde 1 no lu bir API’ye std::string geçirme ihtiyacı olduğunda, burada c_str() API’sini çağırmanız gerekmekte,
  • Benzer şekilde elinizdeki std::string kullanan 2 nolu gibi bir API’ye karakter dizisi geçirmek istediğinizde, çoğu durumda derleyici geçici bir string nesnesi oluşturulacaktır.

Yukarıdaki iki durum aslında std::string_view için olası en sık kullanımları teşkil etmekte. Daha fazla detaya girmeden önce, std::string_view‘a bir göz atalım.

std::string_view, salt-okunur karakter dizilerine (yazma hakkında olmadan), herhangi bir sahiplik olmadan erişim sağlayan bir sınıf olarak ifade edebiliriz. Bir diğer ifade ile, var olan bir karakter dizine, görünüm/erişim sağlayan bir sınıftır.

Bu sınıf genel olarak, karakter dizisi verisine bir işaretçi ve bu dizinin boyut bilgisini içerecek şekilde gerçeklenir.

Bu sebeple, bu sınıfın kopyasını oluşturmak, bu sınıfın işaret ettiği veriyi kopyalamayacağı için (“shallow copy“) oldukça hızlı bir şekilde gerçekleştirmektedir. Bu anlamda str::string_view fonksiyonlara direk değer olarak geçirilmelidir (“pass by value“).

Bu sınıf aracılığı ile sunulan temel API’ler aşağıdaki gibi listelenebilir. Dikkat edeceğiniz üzere, bu API’lerin hepsi veriyi değiştirmeyen API’ler. Peki remove_prefix/remove_suffix‘e ne olacak diye aklınıza gelirse, aslında bu API’lerde de arkadaki veri değiştirilmez. Sadece işaretçi ve boyut bilgisi güncellenir, burası önemli. Aynı durum substr(), API’si için de geçerlidir ama ona daha detaylı bakacağız.

  • operator[]
  • at
  • front
  • back
  • data
  • size/length
  • max_size
  • empty
  • remove_prefix
  • remove_suffix
  • swap
  • copy (not constexpr)
  • substr – karmaşıklıkO(1),  std::string ile sunulan ise O(n). Bu API önemli, neden bu şekilde bir fark var, birazdan buna değineceğim.
  • compare
  • find
  • rfind
  • find_first_of
  • find_last_of
  • find_first_not_of
  • find_last_not_of
  • sıralama operatörleri: ==, !=, <=, >=, <, >
  • operator <<

Tam liste ve detaylı açıklamalar için std::basic_string_view adresine göz atabilirsiniz.

Bu sınıf ile ilgili dikkat edilmesi gereken en önemli nokta, işaret ettiği karakter dizisinin yaşam süresi/kapsamı üzerinde hiç bir etkisi yoktur ve bu sorumluluk tamamen kullanıcıdadır. Bunun bir şekilde kotarılması, kod bütünlüğü için önemli.

Yukarıdaki tanımlar ışığında, string ve benzeri referansı geçirilen verileri değiştirmeden sadece erişmek istiyorsanız. std::string_view kullanım için tercih edilebilir. Eğer altta yatan veriye erişim ihtiyacı hasıl olursa da, string nesnesine bu string_view nesnesi geçirilebilir.

Şimdi gelelim, yukarıda bahsettiğimiz iki sıkıntılı kullanımı std::string_view nasıl çözüyor. std::string_view, hem karakter dizisi işaretçisi hem de std::string alan yapıcılar barındırmaktadır. İlk durumda (1. no’lu API kullanımı), string_view, ilgili işaretçiyi saklar ve strlen() ile boyut bilgisi saklanır. İkinci durumda ise, zaten std::string‘in barındırdığı işaretçi ve boyut direk saklanır.

std::string_view‘ın boyut anlamında nasıl bir avantaj sağladığını https://skebanga.github.io/string-view/ sitesinde de detaylı bir şekilde verilen bir örnek kod üzerinden inceleyelim. Bu kod üzerinden normalde std::string ve benzeri karakter dizilerinin kullanımından ötürü ne kadar dinamik bellek alındığını ve std::string_view ile bunların ne kadar azaltıldığını göreceğiz.

Bu kodu ben çalıştırdığımda aşağıdaki çıktıyı alıyorum:

Sizin de göreceğiniz üzere toplamda 5 * 39 byte’lık bir yer alınıyor. Sadece bir kaç karşılaştırma için! Şimdi de aynı kodu std::string_view kullanacak şekilde güncelleyip bir göz atalım:

Göreceğiniz üzere sadece compare metodunu güncelliyoruz ve bu durumda elde ettiğimiz çıktı da aşağıdaki gibi:

Göreceğiniz üzere artık sadece bir kere bellek alınıyor! Bu kullanımın sağlayacağı bellek ve performans kazancını size bırakıyorum. Bu kodta sadece str nesnesi için bir yer alınıyor ve diğer sabit karakter dizileri için herhangi bir yer alınmıyor.

Yukarıdaki örnek için bir diğer avantaj da aslında, farklı tipte karakter dizisi ve string tiplerinden kurtulmamız. Örneğin, önceki yazılarımda olduğu gibi QT kullanıyorsanız, yukarıdaki tek bir compare fonksiyonu için aşağıdaki fonksiyonların hepsini tanımlanmaya ihtiyaç duyabilirsiniz!

std::string_view‘ın buraya kadar anlattığım kullanımları yanında performans anlamında daha göze çarpan avantaj sağladığı bir diğer durum da substr() ve benzeri API’lerin kullanımında ortaya çıkmaktadır.

Hemen bir örnek kod üzerinden buna da bakalım:

Performans anlamında somut bilgi ve analizler için kaynaklar kısmındaki sitelere bir göz atabilirsiniz. Sizlere bu sınıfın doğru kullanımında neler kazandıracağı hakkında fikir verecektir.

Evet, sevgili yazılımperver dostlarım, bir C++ yazımın daha sonuna geldik. Bir sonraki yazımda görüşmek dileğiyle.

Kaynaklar

 

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.