Kapan saya harus menggunakan string_view di antarmuka?

16

Saya menggunakan perpustakaan internal yang dirancang untuk meniru perpustakaan C ++ yang diusulkan , dan kadang-kadang dalam beberapa tahun terakhir saya melihat antarmuka berubah dari menggunakan std::stringke string_view.

Jadi saya dengan patuh mengubah kode saya, agar sesuai dengan antarmuka baru. Sayangnya, yang harus saya sampaikan adalah parameter std :: string, dan sesuatu yang merupakan nilai pengembalian std :: string. Jadi kode saya berubah dari sesuatu seperti ini:

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

untuk

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()));
}

Saya benar - benar tidak melihat perubahan apa yang saya beli sebagai klien API, selain lebih banyak kode (untuk kemungkinan gagal). Panggilan API kurang aman (karena API tidak lagi memiliki penyimpanan untuk parameternya), mungkin menyimpan program saya 0 bekerja (karena pemindahan optimisasi dapat dilakukan oleh kompiler sekarang), dan bahkan jika itu memang menghemat pekerjaan, itu hanya akan menjadi beberapa alokasi yang tidak dan tidak akan pernah dilakukan setelah startup atau dalam lingkaran besar di suatu tempat. Bukan untuk API ini.

Namun, pendekatan ini tampaknya mengikuti saran yang saya lihat di tempat lain, misalnya jawaban ini :

Sebagai tambahan, karena C ++ 17 Anda harus menghindari melewatkan const std :: string & yang mendukung std :: string_view:

Saya menemukan saran yang mengejutkan, karena tampaknya mengadvokasi secara universal mengganti objek yang relatif aman dengan yang kurang aman (pada dasarnya pointer dan panjang yang dimuliakan), terutama untuk keperluan optimasi.

Jadi kapan seharusnya string_view digunakan, dan kapan seharusnya tidak?

TED
sumber
1
Anda seharusnya tidak perlu memanggil std::string_viewkonstruktor secara langsung, Anda hanya harus meneruskan string ke metode yang mengambil std::string_viewlangsung dan itu akan secara otomatis dikonversi.
Mgetz
@Mgetz - Hmmm. Saya belum (belum) menggunakan kompiler C ++ 17 yang lengkap, jadi mungkin itulah sebagian besar masalahnya. Namun, kode sampel di sini tampaknya menunjukkan diperlukan, setidaknya ketika menyatakan satu.
TED
4
Lihat jawaban saya operator konversi ada di <string>header dan terjadi secara otomatis. Kode itu menipu dan salah.
Mgetz
1
"dengan yang kurang aman" bagaimana irisan kurang aman daripada referensi string?
CodesInChaos
3
@ TED Penelepon dapat dengan mudah membebaskan string yang ditunjuk oleh referensi Anda karena mereka dapat membebaskan memori yang ditunjukkan oleh slice.
CodesInChaos

Jawaban:

18
  1. Apakah fungsionalitas yang mengambil nilai perlu dimiliki oleh string? Jika demikian gunakan std::string(non-const, non-ref). Opsi ini memberi Anda pilihan untuk secara eksplisit bergerak dalam nilai juga jika Anda tahu bahwa itu tidak akan pernah digunakan lagi dalam konteks panggilan.
  2. Apakah fungsinya baru saja membaca string? Jika demikian gunakan std::string_view(const, non-ref) ini karena string_viewdapat menangani std::stringdan char*dengan mudah tanpa masalah dan tanpa membuat salinan. Ini harus mengganti semua const std::string&parameter.

Pada akhirnya Anda seharusnya tidak perlu memanggil std::string_viewkonstruktor seperti Anda. std::stringmemiliki operator konversi yang menangani konversi secara otomatis.

Mgetz
sumber
Hanya untuk memperjelas satu poin, saya pikir operator konversi seperti itu juga akan menangani masalah terburuk seumur hidup, dengan memastikan nilai string RHS Anda tetap ada selama panggilan berlangsung?
TED
3
@ TED jika Anda hanya membaca nilai maka nilainya akan bertahan lebih lama dari panggilan. Jika Anda mengambil kepemilikan maka itu perlu lebih lama dari panggilan. Karena itu mengapa saya membahas kedua kasus tersebut. Operator konversi hanya berurusan dengan membuat std::string_viewlebih mudah digunakan. Jika pengembang salah menggunakannya dalam situasi memiliki itu kesalahan pemrograman. std::string_viewbenar-benar tidak memiliki.
Mgetz
Mengapa const, non-ref? Parameter yang menjadi const tergantung pada penggunaan spesifik, tetapi secara umum masuk akal sebagai non-const. Dan Anda melewatkan 3. Dapat menerima irisan
v.oddou
Apa masalah lewat const std::string_view &di tempat const std::string &?
ceztko
@ceztko sama sekali tidak perlu dan menambahkan tipuan ekstra saat mengakses data.
Mgetz
15

A std::string_viewmembawa beberapa manfaat a const char*ke C ++: tidak seperti std::string, string_view

  • tidak memiliki memori,
  • tidak mengalokasikan memori,
  • dapat menunjuk ke string yang ada di beberapa offset, dan
  • memiliki satu tingkat tipuan pointer kurang dari a std::string&.

Ini berarti string_view sering dapat menghindari salinan, tanpa harus berurusan dengan pointer mentah.

Dalam kode modern, std::string_viewharus mengganti hampir semua penggunaan const std::string&parameter fungsi. Ini harus menjadi perubahan yang kompatibel dengan sumber, karena std::stringmenyatakan operator konversi untuk std::string_view.

Hanya karena tampilan string tidak membantu dalam kasus penggunaan spesifik Anda di mana Anda perlu membuat string, tidak berarti bahwa itu adalah ide yang buruk secara umum. Pustaka standar C ++ cenderung dioptimalkan untuk umum daripada untuk kenyamanan. Argumen "kurang aman" tidak berlaku, karena tidak perlu membuat sendiri tampilan string.

amon
sumber
2
Kelemahan utama std::string_viewadalah tidak adanya c_str()metode, menghasilkan std::stringobjek perantara yang tidak perlu yang perlu dibangun dan dialokasikan. Ini khususnya masalah dalam API tingkat rendah.
Matthias
1
@Matithias Itu poin yang bagus, tapi saya pikir itu bukan kelemahan besar. Tampilan string memungkinkan Anda untuk menunjuk ke string yang ada di beberapa offset. Substring itu tidak bisa diakhiri nol, Anda perlu salinan untuk itu. Tampilan string tidak melarang Anda membuat salinan. Ini memungkinkan banyak tugas pemrosesan string yang dapat dilakukan dengan iterator. Tapi Anda benar bahwa API yang membutuhkan string C tidak akan mendapat untung dari tampilan. Referensi string dapat lebih sesuai.
amon
@ Matthias, bukankah string_view :: data () cocok dengan c_str ()?
Aelian
3
@ Jeevaka string C harus diakhiri nol, tetapi data tampilan string biasanya tidak diakhiri nol karena menunjuk ke string yang sudah ada. Misalnya jika kita memiliki string abcdef\0dan tampilan string yang menunjuk pada cdesubstring, tidak ada karakter nol setelah e- string asli memiliki di fsana. The standar catatan juga: “Data () dapat kembali pointer ke buffer yang tidak null-dihentikan. Oleh karena itu biasanya merupakan kesalahan untuk meneruskan data () ke fungsi yang hanya memerlukan karakter konstanta * dan mengharapkan string yang diakhiri dengan null. "
amon
1
@kayleeFrye_onDeck Data sudah merupakan pointer char. Masalah dengan string C bukanlah mendapatkan pointer char, tetapi string C harus diakhiri null. Lihat komentar saya sebelumnya untuk contoh.
amon
8

Saya menemukan saran yang mengejutkan, karena tampaknya mengadvokasi secara universal mengganti objek yang relatif aman dengan yang kurang aman (pada dasarnya pointer dan panjang yang dimuliakan), terutama untuk keperluan optimasi.

Saya pikir ini sedikit salah mengerti tujuan dari ini. Meskipun ini merupakan "optimasi", Anda harus benar-benar menganggapnya sebagai melepaskan diri dari keharusan menggunakan std::string.

Pengguna C ++ telah membuat lusinan kelas string yang berbeda. Kelas string dengan panjang tetap, kelas yang dioptimalkan SSO dengan ukuran buffer sebagai parameter templat, kelas string yang menyimpan nilai hash yang digunakan untuk membandingkannya, dll. Beberapa orang bahkan menggunakan string berbasis-SAP. Jika ada satu hal yang suka dilakukan oleh programmer C ++, ia menulis kelas string.

Dan itu mengabaikan string yang dibuat dan dimiliki oleh perpustakaan C. Naked char*, mungkin dengan ukuran tertentu.

Jadi, jika Anda menulis pustaka, dan Anda mengambil const std::string&, pengguna sekarang harus mengambil string apa pun yang mereka gunakan dan menyalinnya ke a std::string. Mungkin puluhan kali.

Jika Anda ingin akses ke std::stringantarmuka spesifik string, mengapa Anda harus menyalin string? Itu sia-sia.

Alasan prinsip untuk tidak mengambil string_viewsebagai parameter adalah:

  1. Jika tujuan akhir Anda adalah meneruskan string ke antarmuka yang mengambil string yang diakhiri NUL ( fopen, dll). std::stringdijamin akan dihentikan NUL; string_viewbukan. Dan sangat mudah untuk membuat substring view agar non-NUL dihentikan; sub-stringing a std::stringakan menyalin substring keluar ke rentang NUL-dihentikan.

    Saya menulis jenis gaya string_view khusus NUL-dihentikan untuk persis skenario ini. Anda dapat melakukan sebagian besar operasi, tetapi bukan operasi yang melanggar statusnya yang diakhiri NUL (misalnya, memotong dari bagian akhir).

  2. Masalah seumur hidup. Jika Anda benar-benar perlu menyalinnya std::stringatau memiliki array karakter yang lebih lama dari panggilan fungsi, yang terbaik adalah menyatakan ini di muka dengan mengambil a const std::string &. Atau hanya std::stringsebagai parameter nilai. Dengan begitu, jika mereka sudah memiliki string seperti itu, Anda dapat mengklaim kepemilikannya segera, dan penelepon dapat pindah ke string jika mereka tidak perlu menyimpan salinannya.

Nicol Bolas
sumber
Apakah ini benar? Satu-satunya kelas string standar yang saya ketahui dalam C ++ sebelum ini adalah std :: string. Ada beberapa dukungan untuk menggunakan char * sebagai "string" untuk kompatibilitas dengan C, tetapi saya hampir tidak perlu menggunakannya. Tentu, ada banyak kelas pihak ketiga yang ditentukan pengguna untuk hampir semua yang dapat Anda bayangkan, dan string mungkin termasuk di dalamnya, tapi saya hampir tidak pernah harus menggunakannya.
TED
@ TED: Hanya karena Anda "hampir tidak pernah harus menggunakan itu" tidak berarti bahwa orang lain tidak menggunakannya secara rutin. string_viewadalah jenis bahasa perancis yang dapat bekerja dengan apa saja.
Nicol Bolas
3
@ TED: Itu sebabnya saya mengatakan "C ++ sebagai lingkungan pemrograman", sebagai lawan dari "C ++ sebagai bahasa / perpustakaan."
Nicol Bolas
2
@ TED: " Jadi saya bisa sama-sama mengatakan" C ++ sebagai lingkungan pemrograman memiliki ribuan kelas kontainer "? " Dan ya. Tapi saya bisa menulis algoritma yang bekerja dengan iterator, dan setiap kelas kontainer yang mengikuti paradigma itu akan bekerja dengannya. Sebaliknya, "algoritma" yang dapat mengambil array karakter yang berdekatan jauh lebih sulit untuk ditulis. Dengan string_view, itu mudah.
Nicol Bolas
1
@ TED: Susunan karakter adalah kasus yang sangat spesial. Mereka sangat umum, dan wadah yang berbeda dari karakter yang berdekatan hanya berbeda dalam bagaimana mereka mengelola memori mereka, bukan pada bagaimana Anda mengulangi seluruh data. Jadi memiliki satu jenis rentang bahasa tunggal yang dapat mencakup semua kasus seperti itu tanpa harus menggunakan templat masuk akal. Generalisasi di luar ini adalah provinsi dari Range TS dan templat.
Nicol Bolas