vektor :: di vs. vektor :: operator []

95

Saya tahu itu at()lebih lambat daripada []karena pemeriksaan batasnya, yang juga dibahas dalam pertanyaan serupa seperti C ++ Vector pada / [] kecepatan operator atau :: std :: vector :: at () vs operator [] << hasil yang mengejutkan !! 5 hingga 10 kali lebih lambat / lebih cepat! . Saya hanya tidak mengerti untuk apa at()metode itu baik.

Jika saya memiliki vektor sederhana seperti ini: std::vector<int> v(10);dan saya memutuskan untuk mengakses elemennya dengan menggunakan at()alih-alih []dalam situasi ketika saya memiliki indeks idan saya tidak yakin apakah itu dalam batas vektor, itu memaksa saya untuk membungkusnya dengan try-catch blok :

try
{
    v.at(i) = 2;
}
catch (std::out_of_range& oor)
{
    ...
}

meskipun saya dapat melakukan perilaku yang sama dengan menggunakan size()dan memeriksa indeks saya sendiri, yang tampaknya lebih mudah dan nyaman bagi saya:

if (i < v.size())
    v[i] = 2;

Jadi pertanyaan saya adalah:
Apa keuntungan menggunakan vector :: at over vector :: operator [] ?
Kapan saya harus menggunakan vector :: at daripada vector :: size + vector :: operator [] ?

LihO
sumber
11
+1 pertanyaan yang sangat bagus !! tetapi saya tidak berpikir at () adalah yang umum digunakan.
Rohit Vipin Mathews
10
Perhatikan bahwa dalam kode contoh Anda if (i < v.size()) v[i] = 2;, ada kemungkinan jalur kode yang tidak ditetapkan 2ke elemen apa pun vsama sekali. Jika itu perilaku yang benar, bagus. Tetapi seringkali tidak ada yang masuk akal bahwa fungsi ini dapat dilakukan kapan i >= v.size(). Jadi tidak ada alasan khusus mengapa tidak menggunakan pengecualian untuk menunjukkan situasi yang tidak terduga. Banyak fungsi yang hanya digunakan operator[]tanpa mencentang ukuran, dokumen yang iharus berada dalam jangkauan, dan menyalahkan UB yang dihasilkan pada pemanggil.
Steve Jessop

Jawaban:

74

Saya akan mengatakan pengecualian yang vector::at()melempar tidak benar-benar dimaksudkan untuk ditangkap oleh kode sekitarnya. Mereka terutama berguna untuk menangkap bug dalam kode Anda. Jika Anda perlu memeriksa batas saat runtime karena misalnya indeks berasal dari input pengguna, Anda memang paling baik dengan ifpernyataan. Singkatnya, rancang kode Anda dengan tujuan yang vector::at()tidak akan pernah mengeluarkan pengecualian, sehingga jika ya, dan program Anda dibatalkan, itu adalah tanda bug. (seperti assert())

pmdj
sumber
1
+1 Saya suka penjelasan tentang cara memisahkan penanganan input pengguna yang salah (validasi input; input yang tidak valid mungkin diharapkan sehingga tidak dianggap sebagai sesuatu yang luar biasa) ... dan bug dalam kode (iterator dereferencing yang di luar jangkauan sangat luar biasa hal)
Bojan Komazec
Jadi, Anda mengatakan bahwa saya harus menggunakan size()+ []ketika indeks bergantung pada input pengguna, gunakan assertdalam situasi di mana indeks tidak boleh keluar dari batas untuk perbaikan bug yang mudah di masa mendatang dan .at()dalam semua situasi lainnya (untuk berjaga-jaga, menyebabkan sesuatu yang salah mungkin terjadi .. .)
LihO
8
@LihO: jika implementasi Anda menawarkan implementasi debugging vectormaka mungkin lebih baik menggunakannya sebagai opsi "berjaga-jaga" daripada di at()mana - mana. Dengan begitu, Anda dapat mengharapkan kinerja yang lebih baik dalam mode rilis, jika Anda membutuhkannya.
Steve Jessop
3
Ya, sebagian besar implementasi STL saat ini mendukung mode debug yang bahkan memeriksa batas operator[], misalnya gcc.gnu.org/onlinedocs/libstdc++/manual/… jadi jika platform Anda mendukung ini, Anda mungkin lebih baik melakukannya!
pmdj
1
@pmdj poin yang fantastis, yang saya tidak tahu tentang ... tetapi tautan yatim piatu. : P saat ini adalah: gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html
underscore_d
16

itu memaksa saya untuk membungkusnya dengan blok coba-tangkap

Tidak, tidak (blok coba / tangkap bisa hulu). Ini berguna ketika Anda ingin pengecualian dilemparkan daripada program Anda memasuki dunia perilaku tidak terdefinisi.

Saya setuju bahwa sebagian besar akses di luar batas ke vektor adalah kesalahan programmer (dalam hal ini Anda harus menggunakan assertuntuk menemukan kesalahan tersebut dengan lebih mudah; sebagian besar versi debug dari pustaka standar melakukan ini secara otomatis untuk Anda). Anda tidak ingin menggunakan pengecualian yang dapat ditelan untuk melaporkan kesalahan pemrogram: Anda ingin dapat memperbaiki bug .

Karena tidak mungkin akses di luar batas ke vektor merupakan bagian dari alur program normal (dalam kasus ini, Anda benar: periksa terlebih dahulu sizedaripada membiarkan pengecualian menggelembung), saya setuju dengan diagnostik Anda: atpada dasarnya tidak berguna.

Alexandre C.
sumber
Jika saya tidak menangkap out_of_rangepengecualian, maka abort()dipanggil.
LihO
@ LihO: Belum tentu..dapat try..catchada dalam metode yang memanggil metode ini.
Naveen
12
Jika tidak ada yang lain, atberguna sejauh Anda menemukan diri Anda menulis sesuatu seperti itu if (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }. Orang sering memikirkan fungsi pelempar pengecualian dalam istilah "kutukan, saya harus menangani pengecualian", tetapi selama Anda mendokumentasikan dengan cermat apa yang dapat dilemparkan oleh masing-masing fungsi, fungsi tersebut juga dapat digunakan sebagai "hebat, saya tidak harus memeriksa kondisi dan memberikan pengecualian ".
Steve Jessop
@SteveJessop: Saya tidak suka memberikan pengecualian untuk bug program, karena mereka dapat ditangkap upstream oleh programmer lain. Pernyataan jauh lebih berguna di sini.
Alexandre C.
6
@AlexX. baik, tanggapan resmi untuk itu adalah yang out_of_rangeberasal dari logic_error, dan programmer lain "harus" tahu lebih baik daripada untuk menangkap logic_errordan mengabaikan mereka. assertdapat diabaikan juga jika kolega Anda tidak ingin tahu tentang kesalahan mereka, itu hanya lebih sulit karena mereka harus mengkompilasi kode Anda dengan NDEBUG;-) Setiap mekanisme memiliki kelebihan dan kekurangan.
Steve Jessop
11

Apa keuntungan menggunakan vector :: di over vector :: operator []? Kapan saya harus menggunakan vector :: at daripada vector :: size + vector :: operator []?

Poin penting di sini adalah bahwa pengecualian memungkinkan pemisahan aliran kode normal dari logika penanganan kesalahan, dan satu blok tangkap dapat menangani masalah yang dihasilkan dari berbagai situs lemparan, bahkan jika tersebar jauh di dalam pemanggilan fungsi. Jadi, ini tidak at()selalu lebih mudah untuk satu penggunaan, tetapi terkadang itu menjadi lebih mudah - dan tidak terlalu mengaburkan logika kasus normal - ketika Anda memiliki banyak pengindeksan untuk divalidasi.

Patut diperhatikan juga bahwa dalam beberapa jenis kode, indeks bertambah dengan cara yang rumit, dan terus digunakan untuk mencari array. Dalam kasus seperti itu, jauh lebih mudah untuk memastikan penggunaan pemeriksaan yang benar at().

Sebagai contoh dunia nyata, saya memiliki kode yang mengubah C ++ menjadi elemen leksikal, lalu kode lain yang memindahkan indeks ke vektor token. Bergantung pada apa yang ditemui, saya mungkin ingin menambah dan memeriksa elemen berikutnya, seperti di:

if (token.at(i) == Token::Keyword_Enum)
{
    ASSERT_EQ(tokens.at(++i), Token::Idn);
    if (tokens.at(++i) == Left_Brace)
        ...
    or whatever

Dalam situasi seperti ini, sangat sulit untuk memeriksa apakah Anda telah secara tidak mencapai akhir dari input karena itu sangat tergantung pada token tepat ditemui. Pemeriksaan eksplisit pada setiap titik penggunaan itu menyakitkan, dan ada lebih banyak ruang untuk kesalahan programmer karena peningkatan pra / posting, offset pada titik penggunaan, penalaran yang salah tentang validitas lanjutan dari beberapa pengujian sebelumnya, dll.

Tony Delroy
sumber
10

at bisa lebih jelas jika Anda memiliki penunjuk ke vektor:

return pVector->at(n);
return (*pVector)[n];
return pVector->operator[](n);

Selain performa, yang pertama adalah kode yang lebih sederhana dan lebih jelas.

Brangdon
sumber
... terutama saat Anda membutuhkan penunjuk ke elemen ke- n dari sebuah vektor.
lumba
4

Pertama, apakah lebih lambat at()atau operator[]tidak ditentukan. Ketika tidak ada kesalahan batas, saya berharap mereka memiliki kecepatan yang sama, setidaknya dalam proses debugging. Perbedaannya adalah bahwa at()menentukan dengan tepat apa yang akan terjadi jika ada kesalahan batas (pengecualian), sedangkan dalam kasus operator[], ini adalah perilaku yang tidak ditentukan — kerusakan di semua sistem yang saya gunakan (g ++ dan VC ++), setidaknya ketika flag debugging normal digunakan. (Perbedaan lainnya adalah bahwa setelah saya yakin dengan kode saya, saya bisa mendapatkan peningkatan kecepatan yang substansial operator[] dengan mematikan debugging. Jika kinerja memerlukannya — saya tidak akan melakukannya kecuali diperlukan.)

Dalam praktiknya, at()jarang sekali sesuai. Jika konteksnya sedemikian sehingga Anda tahu bahwa indeks mungkin tidak valid, Anda mungkin menginginkan pengujian eksplisit (misalnya mengembalikan nilai default atau sesuatu), dan jika Anda tahu bahwa itu tidak mungkin tidak valid, Anda ingin membatalkannya (dan jika Anda tidak tahu apakah itu tidak valid atau tidak, saya sarankan Anda menentukan antarmuka fungsi Anda dengan lebih tepat). Namun, ada beberapa pengecualian, di mana indeks yang tidak valid dapat dihasilkan dari penguraian data pengguna, dan kesalahan tersebut seharusnya menyebabkan pembatalan seluruh permintaan (tetapi tidak membuat server turun); dalam kasus seperti itu, pengecualian sesuai, dan at()akan melakukannya untuk Anda.

James Kanze
sumber
4
Mengapa Anda mengharapkan mereka memiliki kecepatan yang sama, ketika operator[]tidak dipaksa untuk memeriksa batas, padahal at()sebenarnya? Apakah Anda menyiratkan masalah penyangga cache, spekulasi, dan bercabang?
Sebastian Mach
3
Maaf, Anda melewatkan atribut "dalam mode debugging". Namun, saya tidak akan mengukur kualitas kode dalam mode debug. Dalam mode rilis, pemeriksaan hanya diperlukan oleh at().
Sebastian Mach
1
@phresnel Sebagian besar kode yang saya kirimkan berada dalam mode "debug". Anda hanya mematikan pemeriksaan ketika masalah kinerja benar-benar membutuhkannya. (Microsoft pra-2010 adalah sedikit masalah di sini, karena std::stringtidak selalu berfungsi jika opsi pemeriksaan tidak sesuai dengan yang ada pada runtime:, -MDdan Anda sebaiknya mematikan pemeriksaan -MDd, dan Anda sebaiknya memiliki itu menyala.)
James Kanze
3
Saya lebih dari kamp yang mengatakan "kode sebagai sanksi (dijamin) oleh standar"; tentu saja Anda bebas untuk mengirimkan dalam mode debug, tetapi ketika melakukan pengembangan lintas platform (termasuk, tetapi tidak secara eksklusif, kasus OS yang sama, tetapi versi kompiler yang berbeda), mengandalkan standar adalah pilihan terbaik untuk rilis, dan mode debug dianggap sebagai alat bagi programmer untuk mendapatkan hal itu sebagian besar benar dan kuat :)
Sebastian Mach
1
Di sisi lain, jika bagian penting dari aplikasi Anda diisolasi dan dilindungi oleh misalnya keamanan pengecualian (RAII ftw), maka haruskah setiap akses operator[]dilumpuhkan? Misalnya std::vector<color> surface(witdh*height); ...; for (int y=0; y!=height; ++y)...,. Saya pikir menegakkan pemeriksaan batas pada biner yang dikirim berada di bawah pesimisasi dini. Imho, itu seharusnya hanya menjadi bantuan pita untuk kode yang tidak direkayasa dengan baik.
Sebastian Mach
1

Inti dari menggunakan pengecualian adalah kode penanganan kesalahan Anda bisa lebih jauh.

Dalam kasus khusus ini, masukan pengguna memang merupakan contoh yang baik. Bayangkan Anda ingin menganalisis secara semantik struktur data XML yang menggunakan indeks untuk merujuk ke beberapa jenis sumber daya yang Anda simpan secara internal di file std::vector. Sekarang pohon XML adalah pohon, jadi Anda mungkin ingin menggunakan rekursi untuk menganalisisnya. Jauh di lubuk hati, dalam rekursi, mungkin ada pelanggaran akses oleh penulis file XML. Dalam hal ini, Anda biasanya ingin keluar dari semua tingkat rekursi dan hanya menolak seluruh file (atau jenis struktur "yang lebih kasar"). Di sinilah di berguna. Anda tinggal menulis kode analisis seolah-olah file tersebut valid. Kode perpustakaan akan menangani deteksi kesalahan dan Anda dapat menangkap kesalahan pada tingkat kasar.

Juga, kontainer lain, seperti std::map, juga memiliki std::map::atyang memiliki semantik yang sedikit berbeda dari std::map::operator[]: at dapat digunakan pada peta const, sementara operator[]tidak bisa. Sekarang jika Anda ingin menulis kode agnostik kontainer, seperti sesuatu yang dapat menangani salah satu const std::vector<T>&atau const std::map<std::size_t, T>&, ContainerType::atakan menjadi senjata pilihan Anda.

Namun, semua kasus ini biasanya muncul saat menangani beberapa jenis input data yang tidak divalidasi. Jika Anda yakin tentang rentang valid Anda, seperti biasanya, Anda biasanya dapat menggunakan operator[], tetapi lebih baik lagi, iterator dengan begin()dan end().

ltjax
sumber
1

Menurut artikel ini , selain performa, tidak ada bedanya untuk digunakan atatau operator[], hanya jika akses dijamin berada dalam ukuran vektor. Sebaliknya, jika akses hanya berdasarkan pada kapasitas vektor maka lebih aman digunakan at.

ahj
sumber
2
di luar sana ada naga. apa yang terjadi jika kita mengklik link itu? (petunjuk: Saya sudah mengetahuinya, tetapi di StackOverflow kami lebih memilih komentar yang tidak mengalami pembusukan tautan, yaitu berikan ringkasan singkat tentang apa yang ingin Anda katakan)
Sebastian Mach
Terima kasih atas tipnya. Sudah diperbaiki sekarang.
ahj
0

Catatan: Tampaknya beberapa orang baru meremehkan jawaban ini tanpa memberi tahu apa yang salah. Jawaban di bawah ini benar dan dapat diverifikasi di sini .

Hanya ada satu perbedaan: atapakah memeriksa batas sedangkan operator[]tidak. Ini berlaku untuk build debug serta build rilis dan ini ditentukan dengan sangat baik oleh standar. Sesederhana itu.

Ini membuat atmetode menjadi lebih lambat tetapi juga merupakan saran yang sangat buruk untuk tidak digunakan at. Anda harus melihat angka absolut, bukan angka relatif. Saya dapat dengan aman bertaruh bahwa sebagian besar kode Anda melakukan operasi yang lebih mahal daripada at. Secara pribadi, saya mencoba menggunakan atkarena saya tidak ingin bug jahat membuat perilaku tidak terdefinisi dan menyelinap ke produksi.

Shital Shah
sumber
1
Pengecualian di C ++ dimaksudkan sebagai mekanisme penanganan error, bukan alat untuk debugging. Herb Sutter menjelaskan mengapa melempar std::out_of_rangeatau bentuk apa pun std::logic_errorsebenarnya adalah kesalahan logika dalam dan dari dirinya sendiri di sini .
Big Temp
@BigTemp - Saya tidak yakin bagaimana komentar Anda terkait dengan pertanyaan dan jawaban ini. Ya, pengecualian adalah topik yang sangat diperdebatkan tetapi pertanyaannya di sini adalah perbedaan antara atdan []dan jawaban saya hanya menyatakan perbedaannya. Saya pribadi menggunakan metode "aman" ketika kinerja tidak menjadi masalah. Seperti yang dikatakan Knuth, jangan lakukan pengoptimalan prematur. Selain itu, ada baiknya untuk menangkap bug lebih awal daripada dalam produksi terlepas dari perbedaan filosofis.
Shital Shah