Menurut pemahaman saya, copy-on-write bukanlah cara yang layak untuk mengimplementasikan kepatuhan std::string
dalam C ++ 11, tetapi ketika muncul dalam diskusi baru-baru ini saya mendapati diri saya tidak dapat secara langsung mendukung pernyataan itu.
Apakah saya benar bahwa C ++ 11 tidak menerima implementasi berbasis COW std::string
?
Jika ya, apakah batasan ini secara eksplisit dinyatakan di suatu tempat dalam standar baru (di mana)?
Atau apakah pembatasan ini tersirat, dalam arti bahwa itu adalah efek gabungan dari persyaratan baru std::string
yang menghalangi implementasi berbasis KK std::string
. Dalam hal ini, saya akan tertarik pada gaya bab dan ayat derivasi dari 'C ++ 11 secara efektif melarang std::string
implementasi berbasis COW '.
Jawaban:
Itu tidak diperbolehkan, karena sesuai standar 21.4.1 p6, pembatalan iterator / referensi hanya diperbolehkan untuk
Untuk string COW, memanggil non-const
operator[]
akan memerlukan pembuatan salinan (dan referensi yang tidak valid), yang tidak diizinkan oleh paragraf di atas. Oleh karena itu, tidak lagi legal untuk memiliki string COW di C ++ 11.sumber
std::string a("something"); char& c1 = a[0]; std::string b(a); char& c2 = a[1];
c1 mengacu pada a. Anda kemudian "menyalin" a. Kemudian, saat Anda mencoba mengambil referensi untuk kedua kalinya, referensi tersebut harus membuat salinan untuk mendapatkan referensi non-const karena ada dua string yang mengarah ke buffer yang sama. Ini harus membatalkan referensi pertama yang diambil, dan bertentangan dengan bagian yang dikutip di atas.operator[]
(1) harus membuat salinan dan (2) ilegal untuk melakukannya. Manakah dari dua poin itu yang tidak Anda setujui? Melihat komentar pertama Anda, tampaknya sebuah implementasi dapat membagikan string tersebut, setidaknya di bawah persyaratan ini, hingga saat itu diakses, tetapi akses baca dan tulis harus tidak membagikannya. Apakah itu alasan Anda?Jawaban oleh Dave S dan gbjbaanb yang benar . (Dan pernyataan Luc Danton juga benar, meskipun ini lebih merupakan efek samping dari pelarangan string KK daripada aturan asli yang melarangnya.)
Tetapi untuk menjernihkan beberapa kebingungan, saya akan menambahkan beberapa eksposisi lebih lanjut. Berbagai komentar tertaut ke komentar saya di bugzilla GCC yang memberikan contoh berikut:
Inti dari contoh itu adalah untuk mendemonstrasikan mengapa string referensi GCC dihitung (COW) tidak valid di C ++ 11. Standar C ++ 11 membutuhkan kode ini untuk bekerja dengan benar. Tidak ada dalam kode yang mengizinkan
p
untuk menjadi tidak valid di C ++ 11.Menggunakan
std::string
implementasi lama dengan referensi terhitung GCC , kode tersebut memiliki perilaku yang tidak ditentukan, karenap
tidak valid, menjadi penunjuk yang menggantung. (Apa yang terjadi adalah ketikas2
dikonstruksi, ia membagikan datanyas
, tetapi mendapatkan referensi non-const melaluis[0]
mengharuskan datanya tidak dibagikan, begitus
juga "salinan saat menulis" karena referensis[0]
tersebut berpotensi dapat digunakan untuk menuliss
, lalus2
pergi keluar dari ruang lingkup, menghancurkan array yang ditunjukkan olehp
).Standar C ++ 03 secara eksplisit mengizinkan perilaku tersebut di 21.3 [lib.basic.string] p5 di mana dikatakan bahwa setelah panggilan ke
data()
panggilan pertamaoperator[]()
dapat membatalkan pointer, referensi, dan iterator. Jadi string COW GCC adalah implementasi C ++ 03 yang valid.Standar C ++ 11 tidak lagi mengizinkan perilaku itu, karena tidak ada panggilan ke yang
operator[]()
dapat membatalkan pointer, referensi atau iterator, terlepas dari apakah mereka mengikuti panggilan kedata()
.Jadi contoh di atas harus berfungsi di C ++ 11, tetapi tidak berfungsi dengan string COW jenis libstdc ++, oleh karena itu jenis string COW tersebut tidak diizinkan di C ++ 11.
sumber
.data()
(dan setiap pengembalian pointer, referensi, atau iterator) tidak mengalami masalah tersebut. Yaitu (invariant) buffer kapan saja tidak dibagikan, atau dibagikan tanpa referensi eksternal. Saya pikir Anda bermaksud komentar tentang contoh ini sebagai laporan bug informal-sebagai-komentar, maaf karena kesalahpahaman! Tetapi seperti yang Anda lihat dengan mempertimbangkan implementasi seperti yang saya jelaskan di sini, yang berfungsi dengan baik di C ++ 11 ketikanoexcept
persyaratan diabaikan, contoh tidak mengatakan apapun tentang formal. Saya dapat memberikan kode jika Anda mau.std::string
, dan saya benar-benar ragu Anda dapat mendemonstrasikan string COW yang berguna dan berkinerja yang memenuhi persyaratan pembatalan C ++ 11. Jadi saya berpendapat bahwanoexcept
spesifikasi yang ditambahkan pada menit terakhir adalah konsekuensi dari pelarangan string KK, bukan alasan yang mendasarinya. N2668 tampak sangat jelas, mengapa Anda terus menyangkal bukti jelas dari maksud panitia yang diuraikan di sana?data()
adalah fungsi anggota const, jadi harus aman untuk memanggil secara bersamaan dengan anggota const lainnya, dan misalnya untuk memanggildata()
secara bersamaan dengan utas lain yang membuat salinan string. Jadi, Anda akan memerlukan semua overhead mutex untuk setiap operasi string, bahkan yang const, atau kompleksitas struktur yang dihitung referensi yang dapat berubah tanpa kunci, dan setelah semua itu Anda hanya mendapatkan berbagi jika Anda tidak pernah memodifikasi atau mengakses string Anda, begitu banyak, banyak string akan memiliki jumlah referensi satu. Harap berikan kode, jangan ragu untuk mengabaikannoexcept
jaminan.basic_string
fungsi anggota, ditambah fungsi gratis. Biaya abstraksi: kode versi ke nol baru yang tidak dioptimalkan ini 50 hingga 100% lebih lambat dengan g ++ dan MSVC. Itu tidak melakukan keamanan utas (cukup mudah memanfaatkanshared_ptr
, menurut saya) dan itu hanya cukup untuk mendukung pengurutan kamus untuk tujuan waktu, tetapi bug modulo itu membuktikan titik bahwa referensi yang dihitungbasic_string
diizinkan, kecuali untuknoexcept
persyaratan C ++ . github.com/alfps/In-principle-demo-of-ref-counted-basic_stringMemang, Kontrak Karya adalah mekanisme yang dapat diterima untuk membuat string lebih cepat ... tapi ...
itu membuat kode multithreading lebih lambat (semua penguncian itu untuk memeriksa apakah Anda satu-satunya yang menulis membunuh kinerja saat menggunakan banyak string). Inilah alasan utama kematian kontrak beberapa tahun yang lalu.
Alasan lainnya adalah
[]
operator akan mengembalikan data string kepada Anda, tanpa perlindungan apa pun bagi Anda untuk menimpa string yang diharapkan orang lain tidak berubah. Hal yang sama berlaku untukc_str()
dandata()
.Quick google mengatakan bahwa multithreading pada dasarnya adalah alasan mengapa secara efektif tidak diizinkan (tidak secara eksplisit).
Proposal mengatakan:
diikuti oleh
Tali adalah bagian dari STLPort dan SGI STL.
sumber
Dari 21.4.2 konstruktor basic_string dan operator penugasan [string.cons]
Tabel 64 sangat membantu mendokumentasikan bahwa setelah konstruksi objek melalui konstruktor (copy) ini,
this->data()
memiliki nilai:Ada persyaratan serupa untuk konstruktor serupa lainnya.
sumber
Karena sekarang dijamin bahwa string disimpan secara berdekatan dan Anda sekarang diizinkan untuk mengambil pointer ke penyimpanan internal string, (yaitu & str [0] berfungsi seperti yang akan dilakukan untuk array), tidak mungkin membuat COW yang berguna penerapan. Anda harus membuat salinan untuk terlalu banyak hal. Bahkan hanya menggunakan
operator[]
ataubegin()
pada string non-const akan membutuhkan salinan.sumber
c_str()
) harus O (1) dan tidak boleh melempar, dan tidak boleh memasukkan data race, jadi sangat sulit untuk memenuhi persyaratan tersebut jika Anda malas menggabungkan. Dalam praktiknya, satu-satunya pilihan yang masuk akal adalah selalu menyimpan data yang berdekatan.Apakah COW
basic_string
dilarang di C ++ 11 dan yang lebih baru?Mengenai
Iya.
Mengenai
Hampir secara langsung, dengan persyaratan kompleksitas konstan untuk sejumlah operasi yang memerlukan O ( n ) penyalinan fisik dari data string dalam implementasi KK.
Misalnya untuk fungsi anggota
… Yang dalam implementasi COW akan - keduanya memicu penyalinan data string untuk membatalkan pembagian nilai string, standar C ++ 11 memerlukan
C ++ 11 §21.4.5 / 4 :… Yang mengesampingkan penyalinan data tersebut, dan karenanya, KK.
C ++ 03 didukung implementasi SAPI oleh tidak memiliki persyaratan kompleksitas konstan ini, dan oleh, di bawah kondisi pembatasan tertentu, yang memungkinkan panggilan ke
operator[]()
,at()
,begin()
,rbegin()
,end()
, ataurend()
untuk referensi invalidate, pointer dan iterator mengacu pada item tali, yaitu untuk kemungkinan dikenakan Penyalinan data KK. Dukungan ini telah dihapus di C ++ 11.Apakah COW juga dilarang melalui aturan pembatalan C ++ 11?
Dalam jawaban lain yang pada saat penulisan dipilih sebagai solusi, dan yang sangat disukai dan oleh karena itu dipercaya, ditegaskan bahwa
Pernyataan tersebut tidak benar dan menyesatkan dalam dua hal utama:
const
item yang perlu memicu penyalinan data COW.Tetapi pengakses
const
item juga perlu memicu penyalinan data, karena mereka mengizinkan kode klien untuk membentuk referensi atau petunjuk yang (dalam C ++ 11) tidak diizinkan untuk membatalkannya nanti melalui operasi yang dapat memicu penyalinan data COW.Tetapi dalam implementasi yang benar, penyalinan data COW, pembatalan pembagian nilai string, dilakukan pada titik sebelum ada referensi yang dapat dibatalkan.
Untuk melihat bagaimana implementasi C ++ 11 COW yang benar
basic_string
akan bekerja, ketika persyaratan O (1) yang membuat ini tidak valid diabaikan, pikirkan implementasi di mana string dapat beralih di antara kebijakan kepemilikan. Instance string dimulai dengan kebijakan Sharable. Dengan kebijakan ini aktif, tidak ada referensi item eksternal. Instance dapat bertransisi ke kebijakan Unik, dan harus melakukannya ketika referensi item berpotensi dibuat seperti dengan panggilan ke.c_str()
(setidaknya jika itu menghasilkan pointer ke buffer internal). Dalam kasus umum beberapa contoh berbagi kepemilikan nilai, ini memerlukan penyalinan data string. Setelah transisi ke kebijakan Unik, instance hanya dapat beralih kembali ke Sharable melalui operasi yang membatalkan semua referensi, seperti penetapan.Jadi, walaupun kesimpulan jawaban itu, bahwa string KK dikesampingkan, benar, alasan yang ditawarkan salah dan sangat menyesatkan.
Saya menduga penyebab kesalahpahaman ini adalah catatan non-normatif dalam lampiran C C ++ 11:
C ++ 11 §C.2.11 [diff.cpp03.strings], tentang §21.3:Di sini alasan menjelaskan alasan utama mengapa seseorang memutuskan untuk menghapus dukungan COW khusus C ++ 03. Alasan ini, mengapa , bukanlah bagaimana standar secara efektif melarang penerapan KK. Standar melarang sapi melalui persyaratan O (1).
Singkatnya, aturan pembatalan C ++ 11 tidak mengesampingkan implementasi COW
C ++ 03 §21.3 / 5 yang mencakup dukungan COW "panggilan pertama":std::basic_string
. Tetapi mereka mengesampingkan implementasi COW tak terbatas gaya C ++ 03 yang cukup efisien seperti yang ada di setidaknya salah satu implementasi pustaka standar g ++. Dukungan khusus C ++ 03 COW memungkinkan efisiensi praktis, khususnya menggunakanconst
pengakses item, dengan mengorbankan aturan yang halus dan rumit untuk pembatalan:Aturan-aturan ini sangat rumit dan halus sehingga saya ragu banyak programmer, jika ada, dapat memberikan ringkasan yang tepat. Saya tidak bisa.
Bagaimana jika persyaratan O (1) diabaikan?
Jika persyaratan waktu konstan C ++ 11 pada mis
operator[]
diabaikan, maka COW untukbasic_string
dapat secara teknis layak, tetapi sulit untuk diterapkan.Operasi yang dapat mengakses konten string tanpa melakukan penyalinan data COW meliputi:
+
.<<
.basic_string
sebagai argumen untuk fungsi pustaka standar.Yang terakhir karena pustaka standar diizinkan untuk mengandalkan implementasi pengetahuan dan konstruksi tertentu.
Selain itu, implementasi dapat menawarkan berbagai fungsi non-standar untuk mengakses konten string tanpa memicu penyalinan data COW.
Faktor utama yang menyulitkan adalah bahwa dalam C ++ 11
basic_string
akses item harus memicu penyalinan data (membatalkan pembagian data string) tetapi diperlukan untuk tidak melempar , misalnya C ++ 11 §21.4.5 / 3 “ Throws: Nothing.”. Sehingga tidak dapat menggunakan alokasi dinamis biasa untuk membuat buffer baru untuk penyalinan data COW. Salah satu cara untuk mengatasinya adalah dengan menggunakan heap khusus di mana memori dapat dicadangkan tanpa benar-benar dialokasikan, dan kemudian mencadangkan jumlah yang diperlukan untuk setiap referensi logis ke nilai string. Mencadangkan dan membatalkan reservasi di heap seperti itu dapat menjadi waktu yang konstan, O (1), dan mengalokasikan jumlah yang telah dipesan, dapatnoexcept
. Untuk memenuhi persyaratan standar, dengan pendekatan ini tampaknya perlu ada satu heap berbasis reservasi khusus per pengalokasi yang berbeda.Catatan:
¹ Pengakses
const
item memicu penyalinan data KK karena memungkinkan kode klien untuk mendapatkan referensi atau penunjuk ke data, yang tidak diizinkan untuk dibatalkan oleh penyalinan data selanjutnya yang dipicu oleh misalnya pengakses non-const
item.sumber
std::string
, jika mengabaikan persyaratan O (1), akan menjadi tidak efisien, adalah pendapat Anda. Saya tidak tahu seperti apa performanya, tetapi saya pikir pernyataan itu lebih dikedepankan untuk merasakannya, untuk getaran yang disampaikannya, daripada relevansinya dengan jawaban ini.Saya selalu bertanya-tanya tentang sapi yang tidak berubah: sekali sapi dibuat, saya hanya dapat diubah melalui penugasan dari sapi lain, sehingga akan sesuai dengan standar.
Saya punya waktu untuk mencobanya hari ini untuk tes perbandingan sederhana: peta berukuran N yang dikunci oleh string / sapi dengan setiap node memegang satu set semua string di peta (kami memiliki jumlah objek NxN).
Dengan string berukuran ~ 300 byte dan N = 2000 sapi sedikit lebih cepat dan menggunakan hampir urutan besarnya lebih sedikit memori. Lihat di bawah, ukuran dalam kbs, run b dengan sapi.
sumber