Bir arayüzde string_view'i ne zaman kullanmalıyım?


16

Ben mimik a amacıyla tasarlanmış, bir iç kitaplığı kullanıyorum C ++ kütüphanesi önerdi ve bazen son birkaç yıl içinde gördüğüm kendi arayüzü kullanarak değiştirildi std::stringkadar string_view.

Bu yüzden yeni arayüze uymak için kodumu saygıyla değiştiriyorum. Ne yazık ki, ne geçmek zorunda bir std :: string parametresi ve std :: string dönüş değeri olan bir şeydir. Yani kodum böyle bir şeyden değişti:

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   api.setup (p1, special_number_to_string(p2));
}

için

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   const std::string p2_storage(special_number_to_string(p2));
   api.setup (string_view(&p1[0], p1.size()), string_view(&p2_storage[0], p2_storage.size()));
}

Ben gerçekten (muhtemelen berbat) Bu değişikliğin kod dışındaki API istemcisi olarak bana aldığı görmüyorum. API çağrısı daha az güvenlidir (API artık parametreleri için depolama alanına sahip olmadığından), muhtemelen program 0 çalışmamı kaydetti (derleyiciler şimdi yapabilen hareket optimizasyonları nedeniyle) ve iş kaydetmiş olsa bile, bu sadece başlangıçtan sonra veya bir yerde büyük bir döngüde yapılmayacak ve asla yapılmayacak birkaç tahsis. Bu API için değil.

Ancak, bu yaklaşım başka yerlerde gördüğüm tavsiyelere uyuyor gibi görünüyor, örneğin bu cevap :

Bir yana, C ++ 17 beri bir const std :: string & std :: string_view lehine geçmekten kaçınmalısınız:

Öncelikle optimizasyon amacıyla nispeten güvenli bir nesneyi daha az güvenli bir nesneyle (temelde yüceltilmiş bir işaretçi ve uzunluk) değiştirmeyi evrensel olarak savunuyor gibi göründüğü için bu tavsiyeyi şaşırtıcı buluyorum.

Yani ne zaman gerektiğini string_view kullanılabilir, ve ne zaman gerekmez mi?


1
asla std::string_viewyapıcıyı doğrudan çağırmak zorunda kalmamalısınız, sadece dizeleri std::string_viewdoğrudan alarak yönteme geçirmelisiniz ve otomatik olarak dönüşecektir.
Mgetz

@Mgetz - Hmmm. Ben (henüz) tam gelişmiş bir C ++ 17 derleyici kullanmıyorum, bu yüzden belki de sorunun çoğu. Yine de, örnek kod burada belirtmek gibiydi onun gerekli birini bildirirken en azından.
TED

4
Cevabımı görün, dönüşüm operatörü <string>başlıkta ve otomatik olarak gerçekleşiyor. Bu kod aldatıcı ve yanlış.
Mgetz

1
"daha az güvenli olanla" bir dilim bir dizge referansından daha az güvenlidir?
CodesInChaos

3
@TED ​​Arayan kişi, referansınızın işaret ettiği dizgiyi dilimin işaret ettiği belleği serbest bırakabildiği kadar kolayca serbest bırakabilir.
CodesInChaos

Yanıtlar:


18
  1. Değeri alan işlevin dizenin sahipliğini alması gerekiyor mu? Varsa std::string(sabit olmayan, ref olmayan) kullanın. Bu seçenek, arama bağlamında bir daha kullanılmayacağını biliyorsanız, bir değerde açıkça hareket etme seçeneği sunar.
  2. İşlevler sadece dizeyi okuyor mu? Kullanım Öyleyse std::string_viewbunun nedeni (const olmayan ref) string_viewişleyebilir std::stringve char*sorun olmadan ve bir kopyasını yapmadan kolayca. Bu, tüm const std::string&parametrelerin yerini almalıdır .

Sonuçta yapıcıyı aslastd::string_view sizin gibi çağırmanız gerekmez . std::string, dönüşümü otomatik olarak işleyen bir dönüşüm operatörüne sahiptir.


Sadece bir noktayı açıklığa kavuşturmak için, böyle bir dönüşüm operatörünün RHS string değerinin çağrının tüm uzunluğu boyunca kalmasını sağlayarak, ömür boyu sorunların en kötüsüyle ilgileneceğini düşünüyorum?
TED

3
@TED ​​sadece değeri okuyorsanız, değer çağrıyı geçecektir. Sahiplik alıyorsanız, çağrıdan daha uzun süre dayanması gerekir. Bu yüzden her iki durumu da ele aldım. Dönüşüm operatörü sadece std::string_viewkullanımı kolaylaştırmakla ilgilenir . Bir geliştirici bunu bir sahiplenme durumunda yanlış kullanıyorsa, bu bir programlama hatasıdır. std::string_viewkesinlikle sahipsizdir.
Mgetz

Neden const, non-ref? Sabit olan parametre spesifik kullanıma bağlıdır, ancak genel olarak sabit olmayan olarak makuldür. Ve siz 3'ü
v.oddou

const std::string_view &Yerine geçme problemi nedir const std::string &?
ceztko

@ceztko tamamen gereksizdir ve verilere erişirken fazladan bir dolaylama ekler.
Mgetz

15

A std::string_view, a'nın bazı avantajlarını const char*C ++ ' std::stringa getirir : aksine , bir string_view

  • hafızası yok,
  • bellek ayırmaz,
  • bazı göreli konumlarda mevcut bir dizeye işaret edebilir
  • işaretçi dolaylaması düzeyi a 'dan daha azdır std::string&.

Bu, string_view öğesinin ham işaretçilerle uğraşmak zorunda kalmadan genellikle kopyalardan kaçınabileceği anlamına gelir.

Modern kodda, fonksiyon parametrelerinin std::string_viewneredeyse tüm kullanımlarını değiştirmelidir const std::string&. Bu, std::stringdönüşüm operatörünü bildirdiği için kaynağa uyumlu bir değişiklik olmalıdır std::string_view.

Bir dize görünümünün, yine de bir dize oluşturmanız gereken özel kullanım durumunuzda yardımcı olmaması, bunun genel olarak kötü bir fikir olduğu anlamına gelmez. C ++ standart kütüphanesi kolaylıktan ziyade genelliğe göre optimize edilir. “Daha az güvenli” argümanı tutmaz, çünkü dize görünümünü kendiniz oluşturmanız gerekmez.


2
En büyük dezavantajı, inşa edilmesi ve tahsis edilmesi gereken gereksiz, ara nesnelerle sonuçlanan std::string_viewbir c_str()yöntemin olmamasıdır std::string. Bu, özellikle düşük düzeyli API'larda bir sorundur.
Matthias

1
@Matthias Bu iyi bir nokta, ama bunun büyük bir dezavantaj olduğunu düşünmüyorum. Dize görünümü, bazı ofsetlerde var olan bir dizeyi işaret etmenizi sağlar. Bu alt dize sıfır sonlandırılamaz, bunun için bir kopyasına ihtiyacınız vardır. Dize görünümü, kopya oluşturmanızı engellemez. Yineleyicilerle gerçekleştirilebilen birçok dize işleme görevine izin verir. Ancak, bir C dizesi gerektiren API'lerin görünümlerden faydalanmayacağı konusunda haklısınız. Bir dize başvurusu daha uygun olabilir.
amon

@Matthias, string_view :: data () c_str () ile eşleşmiyor mu?
Aelian

3
@Jeevaka bir C dizesi sıfır sonlandırılmalıdır, ancak bir dize görünümünün verileri genellikle sıfır sonlandırılmaz, çünkü mevcut bir dizeye işaret eder. Örneğin, bir dizgimiz abcdef\0ve cdealt dizeye işaret eden bir dize görünümümüz varsa, e- orijinal dizede bir tane fvarsa sıfır karakter yoktur . Standart aynı zamanda “veriler () boş sonlandırılmış olmayan bir tampon için bir işaretçi döndürebilir. Dolayısıyla bu sadece bir const grafik * alır ve bir boşlukla sonlandırılmış dize umulan bir işleve veri () geçmek için bir hata genellikle “.
amon

1
@kayleeFrye_onDeck Veriler zaten bir char işaretçisi. C dizeleriyle ilgili sorun karakter işaretçisi almıyor, ancak C dizesinin boş sonlandırılması gerekiyor. Bir örnek için önceki yorumuma bakın.
amon

8

Öncelikle optimizasyon amacıyla nispeten güvenli bir nesneyi daha az güvenli bir nesneyle (temelde yüceltilmiş bir işaretçi ve uzunluk) değiştirmeyi evrensel olarak savunuyor gibi göründüğü için bu tavsiyeyi şaşırtıcı buluyorum.

Bunun amacını biraz yanlış anladığını düşünüyorum. Bu bir "optimizasyon" olsa da, bunu gerçekten kullanmak zorunda kalmamak için düşünmelisiniz std::string.

C ++ kullanıcıları düzinelerce farklı dize sınıfı oluşturdular . Sabit uzunluklu dize sınıfları, arabellek boyutu bir şablon parametresi olan SSO için optimize edilmiş sınıflar, bunları karşılaştırmak için kullanılan bir karma değer depolayan dize sınıfları, vb. Bazı insanlar COW tabanlı dizeler bile kullanır. C ++ programcılarının yapmayı sevdiği bir şey varsa, yazma string sınıflarıdır.

Bu da C kütüphanelerinin oluşturduğu ve sahip olduğu dizeleri yok sayar. Çıplak char*s, belki bir tür büyüklükte.

Bu nedenle, bir kütüphane yazıyorsanız ve a alırsanız const std::string&, kullanıcının kullandığı dizeyi alması ve bunu bir std::string. Belki onlarca kez.

std::stringAdlı kullanıcının dizeye özgü arayüzüne erişmek istiyorsanız, dizeyi neden kopyalamanız gerekir ? Bu böyle bir israf.

string_viewA parametresini almamanın temel nedenleri :

  1. Nihai hedefiniz dizeyi NUL sonlu dizeyi ( fopenvb.) Alan bir arabirime iletmekse . std::stringsonlandırılacağı garanti edilir; string_viewdeğil. Ve bir görünümü NUL sonlandırılmamış hale getirmek için alt dizlemek çok kolaydır; a alt dizgisi a std::stringalt dizeyi NUL sonlu bir aralığa kopyalar.

    Tam olarak bu senaryo için özel bir NUL sonlu string_view stil türü yazdım. Çoğu işlemi yapabilirsiniz, ancak NUL sonlandırmalı durumunu bozan işlemleri gerçekleştiremezsiniz (örneğin, sondan kırpma).

  2. Yaşam boyu sorunlar. Bunu gerçekten kopyalamanız gerekiyorsa std::stringveya başka bir deyişle işlev dizisinin dışında bir karakter dizisine sahipseniz, bunu a const std::string &. Veya sadece bir std::stringdeğer parametresi olarak. Bu şekilde, zaten böyle bir dizeye sahiplerse, derhal sahipliğini talep edebilirsiniz ve aracının bir kopyasını saklaması gerekmiyorsa dizeye geçebilir.


Bu doğru mu? Bundan önce C ++ 'da farkında olduğum tek standart string sınıfı std :: string idi. Char * 'ları C ile geriye doğru uyumluluk için "dizeler" olarak kullanmak için bazı destek var, ama neredeyse hiç kullanmam gerekmiyor. Elbette, hayal edebileceğiniz hemen hemen her şey için çok sayıda kullanıcı tanımlı üçüncü taraf sınıfı vardır ve muhtemelen dizeler buna dahildir, ancak bunları neredeyse hiç kullanmam gerekmiyor.
TED

@TED: Sadece "neredeyse hiç kullanmak zorunda kalmamanız" diğer insanların bunları rutin olarak kullanmadığı anlamına gelmez . string_viewher şeyle çalışabilecek bir lingua franca türüdür.
Nicol Bolas

3
@TED: Bu yüzden "bir dil / kütüphane olarak C ++" yerine "bir programlama ortamı olarak C ++" dedim.
Nicol Bolas

2
@TED: " Yani eşit derecede söyleyebilirim" C ++ bir programlama ortamı olarak binlerce kapsayıcı sınıf var "? " Ve öyle. Ancak yineleyicilerle çalışan algoritmalar yazabilirim ve bu paradigmayı izleyen tüm kap sınıfları onlarla çalışacaktır. Buna karşılık, herhangi bir bitişik karakter dizisini alabilen "algoritmalar" yazmak çok daha zordu. İle string_view, kolay.
Nicol Bolas

1
@TED: Karakter dizileri çok özel bir durumdur. Son derece yaygındır ve farklı bitişik karakter kapları, verileri nasıl yinelediğinize göre değil, yalnızca belleklerini yönetme şekillerinde farklılık gösterir. Bu nedenle, bir şablon kullanmak zorunda kalmadan tüm bu durumları kapsayabilecek tek bir lingua franca aralığı tipine sahip olmak mantıklıdır. Bunun ötesinde genelleme, Range TS ve şablonların eyaletidir.
Nicol Bolas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.