Dalam C ++, apakah boleh mencuri sumber daya dari peta yang tidak saya perlukan lagi? Lebih tepatnya, anggap saya punya kunci std::map
with std::string
dan saya ingin membuat vektor dengan mencuri sumber daya map
kunci s yang digunakan std::move
. Perhatikan bahwa akses tulis ke kunci merusak struktur data internal (pemesanan kunci) map
tapi saya tidak akan menggunakannya setelah itu.
Pertanyaan : Dapatkah saya melakukan ini tanpa masalah atau akankah ini menyebabkan bug yang tak terduga misalnya di destructor map
karena saya mengaksesnya dengan cara yang std::map
tidak dimaksudkan?
Berikut ini contoh programnya:
#include<map>
#include<string>
#include<vector>
#include<iostream>
using namespace std;
int main(int argc, char *argv[])
{
std::vector<std::pair<std::string,double>> v;
{ // new scope to make clear that m is not needed
// after the resources were stolen
std::map<std::string,double> m;
m["aLongString"]=1.0;
m["anotherLongString"]=2.0;
//
// now steal resources
for (auto &p : m) {
// according to my IDE, p has type
// std::pair<const class std::__cxx11::basic_string<char>, double>&
cout<<"key before stealing: "<<p.first<<endl;
v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
cout<<"key after stealing: "<<p.first<<endl;
}
}
// now use v
return 0;
}
Ini menghasilkan output:
key before stealing: aLongString
key after stealing:
key before stealing: anotherLongString
key after stealing:
EDIT: Saya ingin melakukan ini untuk seluruh konten peta besar dan menyimpan alokasi dinamis dengan mencuri sumber daya ini.
sumber
const
nilai selalu UB.std::string
memiliki optimasi string pendek. Berarti ada beberapa logika non-sepele pada penyalinan dan pemindahan, bukan hanya penunjuk penunjuk dan sebagai tambahan sebagian besar waktu pemindahan menyiratkan penyalinan - jangan sampai Anda berurusan dengan string yang agak panjang. Perbedaan statistik kecil sekali dan secara umum pasti bervariasi tergantung pada jenis pemrosesan string yang dilakukan.Jawaban:
Anda sedang melakukan perilaku yang tidak terdefinisi, gunakan
const_cast
untuk memodifikasiconst
variabel. Jangan lakukan itu. Alasannyaconst
adalah karena peta diurutkan berdasarkan kunci mereka. Jadi memodifikasi kunci di tempat adalah melanggar asumsi yang mendasari peta dibangun.Anda tidak boleh menggunakan
const_cast
untuk menghapusconst
dari variabel dan memodifikasi variabel itu.Bahwa menjadi kata, C ++ 17 memiliki solusi untuk masalah Anda:
std::map
'sextract
fungsi:Dan jangan
using namespace std;
. :)sumber
map
tidak akan mengeluh jika saya tidak memanggil metode (yang tidak saya lakukan) dan mungkin pemesanan internal tidak penting dalam destruktornya?const
. Memutasi suatuconst
objek adalah UB instan, terlepas dari apakah sesuatu benar-benar mengaksesnya setelahnya.extract
saat menggunakan iterators karena argumen telah mengamortisasi kompleksitas konstan. Beberapa overhead tidak dapat dihindari, tetapi mungkin tidak akan cukup berarti. Jika Anda memiliki persyaratan khusus yang tidak tercakup oleh ini, maka Anda harus menerapkan sendirimap
persyaratan yang memenuhi ini.std
Wadah dimaksudkan untuk aplikasi keperluan umum umum . Wadah tidak dioptimalkan untuk kasus penggunaan khusus.const
? Dalam hal ini dapatkah Anda menunjukkan di mana argumentasi saya salah.Kode Anda mencoba untuk memodifikasi
const
objek, sehingga memiliki perilaku yang tidak terdefinisi, seperti yang ditunjukkan oleh jawaban druckermanly .Beberapa jawaban lain ( phinz's dan Deuchie ) berpendapat bahwa kunci tersebut tidak boleh disimpan sebagai
const
objek karena gagang simpul yang dihasilkan dari penggalian simpul keluar dari peta memungkinkan non-const
akses ke kunci. Kesimpulan ini mungkin tampak masuk akal pada awalnya, tetapi P0083R3 , makalah yang memperkenalkanextract
fungsi), memiliki bagian khusus pada topik ini yang membatalkan argumen ini:(penekanan milikku)
sumber
Saya tidak berpikir itu
const_cast
dan modifikasi mengarah pada perilaku yang tidak terdefinisi dalam kasus ini, tetapi tolong komentari apakah argumentasi ini benar.Jawaban ini mengklaim itu
Jadi garis
v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
dalam pertanyaan tidak mengarah ke UB jika dan hanya jikastring
objekp.first
tidak dibuat sebagai objek const. Sekarang perhatikan bahwa referensi tentangextract
statusJadi jika saya
extract
yangnode_handle
sesuaip
,p
terus tinggal di lokasi penyimpanannya. Tetapi setelah ekstraksi, saya diizinkan untukmove
pergi sumber dayap
seperti dalam kode jawaban druckermanly . Ini berarti bahwap
dan karenanya jugastring
objekp.first
itu tidak dibuat sebagai objek const awalnya.Oleh karena itu, saya berpikir bahwa modifikasi
map
kunci tidak mengarah ke UB dan dari jawaban Deuchie , tampaknya juga struktur pohon yang sekarang rusak (sekarang beberapa kunci string kosong yang sama)map
tidak menyebabkan masalah pada destruktor. Jadi kode dalam pertanyaan harus bekerja dengan baik setidaknya di C ++ 17 di manaextract
metode ini ada (dan pernyataan tentang pointer sisa berlaku berlaku).Memperbarui
Saya sekarang berpandangan bahwa jawaban ini salah. Saya tidak menghapusnya karena direferensikan oleh jawaban lain.
sumber
std::launder
.EDIT: Jawaban ini salah. Komentar yang baik menunjukkan kesalahan, tetapi saya tidak menghapusnya karena telah dirujuk dalam jawaban lain.
@druckermanly menjawab pertanyaan pertama Anda, yang mengatakan bahwa mengubah kunci
map
secara paksa merusak ketertiban di manamap
struktur data internal dibuat (pohon Merah-Hitam). Tetapi aman untuk menggunakanextract
metode ini karena melakukan dua hal: memindahkan kunci dari peta dan kemudian menghapusnya, sehingga tidak mempengaruhi keteraturan peta sama sekali.Pertanyaan lain yang Anda ajukan, apakah akan menimbulkan masalah ketika mendekonstruksi, bukan masalah. Ketika peta mendekonstruksi, ia akan memanggil dekonstruktor dari masing-masing elemennya (mapped_types dll.), Dan
move
metode ini memastikan bahwa aman untuk mendekonstruksi kelas setelah dipindahkan. Jadi jangan khawatir. Singkatnya, itu adalah operasimove
yang memastikan bahwa aman untuk menghapus atau menugaskan kembali beberapa nilai baru ke kelas "pindah". Khusus untukstring
,move
metode ini dapat menetapkan pointer char-nyanullptr
, sehingga tidak menghapus data aktual yang dipindahkan ketika dekonstruktor dari kelas asli dipanggil.Sebuah komentar mengingatkan saya pada poin yang saya abaikan, pada dasarnya dia benar tetapi ada satu hal yang saya tidak sepenuhnya setuju:
const_cast
mungkin bukan UB.const
hanyalah janji antara kompiler dan kami. objek yang dicatat sebagaiconst
masih merupakan objek, sama dengan yang tidak denganconst
, dalam hal jenis dan representasi dalam bentuk biner. Ketikaconst
dibuang, itu harus berperilaku seolah-olah itu adalah kelas yang bisa berubah normal. Sehubungan denganmove
, Jika Anda ingin menggunakannya, Anda harus lulus&
bukanconst &
, jadi karena saya melihat itu bukan UB, itu hanya melanggar janjiconst
dan memindahkan data.Saya juga melakukan dua percobaan, menggunakan MSVC 14.24.28314 dan Dentang 9.0.0 masing-masing, dan mereka menghasilkan hasil yang sama.
keluaran:
Kita dapat melihat bahwa string '2' kosong sekarang, tetapi jelas kami telah melanggar ketertiban peta karena string kosong harus ditempatkan kembali ke depan. Jika kami mencoba menyisipkan, menemukan atau menghapus beberapa titik peta tertentu, itu dapat menyebabkan bencana.
Bagaimanapun, kami mungkin sepakat bahwa itu tidak pernah merupakan ide yang baik untuk memanipulasi data dalam dari setiap kelas yang melewati antarmuka publik mereka. The
find
,insert
,remove
fungsi dan sebagainya mengandalkan kebenaran mereka pada keteraturan struktur data batin, dan itulah alasan mengapa kita harus tinggal jauh dari pikiran mengintip ke dalam.sumber
const
nilai) terjadi sebelumnya. Namun, argumen "move
[fungsi] memastikan bahwa aman untuk mendekonstruksi [objek dari] kelas setelah dipindahkan" tidak berlaku: Anda tidak dapat dengan aman berpindah dariconst
objek / referensi, karena itu membutuhkan modifikasi, yangconst
mencegah. Anda dapat mencoba untuk mengatasi keterbatasan itu dengan menggunakanconst_cast
, tetapi pada titik itu Anda yang terbaik akan masuk ke perilaku implementasi yang sangat spesifik, jika tidak UB.move
fungsi mengambil&
alih - alih aconst&
, jadi jika seseorang bersikeras bahwa dia memindahkan kunci dari peta, dia harus menggunakannyaconst_cast
.const_cast
akan menjengkelkan. Ini mungkin bekerja sebagian besar waktu, tetapi pecahkan kode Anda dengan cara yang halus.extract
, kita diperbolehkan untuk bergerak dari objek yang sama, kan? (lihat jawaban saya)std::launder