Mengapa std :: queue :: pop mengembalikan nilai.?

123

Saya membuka halaman ini tetapi saya tidak bisa mendapatkan alasan yang sama. Di sana disebutkan itu

"lebih bijaksana untuk tidak mengembalikan nilai sama sekali dan meminta klien menggunakan front () untuk memeriksa nilai di depan antrean"

Tapi memeriksa elemen dari front () juga membutuhkan elemen itu untuk disalin di lvalue. Misalnya di segmen kode ini

std::queue<int> myqueue;
int myint;
int result;
std::cin >> myint;
myqueue.push (myint);

/ * di sini sementara akan dibuat di kanan atas yang akan ditetapkan ke hasil, dan jika dikembalikan dengan referensi maka hasil akan dianggap tidak valid setelah operasi pop * /

result = myqueue.front();  //result.
std::cout << ' ' << result;
myqueue.pop();

pada baris kelima objek cout pertama membuat salinan myqueue.front () kemudian menetapkan itu ke hasil. Jadi, apa bedanya, fungsi pop bisa melakukan hal yang sama.

cbinder
sumber
Karena diimplementasikan dengan cara ini (yaitu void std::queue::pop();).
101010
Pertanyaannya sudah dijawab, tapi sebagai sidenote: jika Anda benar-benar ingin pop yang kembali, hal itu dapat dengan mudah diimplementasikan dengan fungsi bebas: ideone.com/lnUzf6
eerorika
1
Tautan Anda ke dokumentasi STL. Tapi Anda bertanya tentang pustaka standar C ++. Hal yang berbeda.
juanchopanza
5
"Tapi memeriksa elemen dari front()juga mengharuskan elemen itu disalin di lvalue" - tidak, tidak. frontmengembalikan referensi, bukan nilai. Anda dapat memeriksa nilai yang diacunya tanpa menyalinnya.
Mike Seymour
1
@KeminZ model yang Anda gambarkan membutuhkan salinan. Mungkin. Jika Anda ingin konsumsi antrian multipleks maka ya, Anda harus membuat salinan sebelum melepaskan kunci pada antrian. Namun, jika Anda hanya peduli tentang memisahkan input dan output, maka Anda tidak memerlukan kunci untuk memeriksa bagian depan. Anda dapat menunggu untuk mengunci sampai Anda selesai menggunakannya dan perlu menelepon pop(). Jika Anda menggunakan std::queue<T, std::list<T>>maka tidak ada kekhawatiran tentang referensi yang diberikan front()tidak valid oleh a push(). Tetapi Anda harus mengetahui pola penggunaan Anda dan harus mendokumentasikan batasan Anda.
jwm

Jawaban:

105

Jadi, apa bedanya, fungsi pop bisa melakukan hal yang sama.

Itu memang bisa melakukan hal yang sama. Alasannya tidak, adalah karena pop yang mengembalikan elemen yang muncul tidak aman jika ada pengecualian (harus mengembalikan berdasarkan nilai dan dengan demikian membuat salinan).

Pertimbangkan skenario ini (dengan implementasi pop yang naif / dibuat-buat, untuk mengilustrasikan maksud saya):

template<class T>
class queue {
    T* elements;
    std::size_t top_position;
    // stuff here
    T pop()
    {
        auto x = elements[top_position];
        // TODO: call destructor for elements[top_position] here
        --top_position;  // alter queue state here
        return x;        // calls T(const T&) which may throw
    }

Jika konstruktor salinan T melempar saat kembali, Anda telah mengubah status antrian ( top_positiondalam implementasi naif saya) dan elemen dihapus dari antrian (dan tidak dikembalikan). Untuk semua maksud dan tujuan (tidak peduli bagaimana Anda menangkap pengecualian dalam kode klien) elemen di bagian atas antrian hilang.

Implementasi ini juga tidak efisien jika Anda tidak membutuhkan nilai yang muncul (yaitu membuat salinan elemen yang tidak akan digunakan siapa pun).

Ini dapat diimplementasikan dengan aman dan efisien, dengan dua operasi terpisah ( void popdan const T& front()).

utnapistim
sumber
hmmm ... itu masuk akal
cbinder
37
Catatan C ++ 11: jika T memiliki konstruktor noexcept yang murah (yang sering terjadi pada jenis objek yang diletakkan di tumpukan) maka mengembalikan dengan nilai adalah efisien dan aman untuk pengecualian.
Roman L
9
@ DavidRodríguez-dribeas: Saya tidak menyarankan perubahan apa pun, maksud saya adalah bahwa beberapa kekurangan yang disebutkan menjadi lebih sedikit masalah dengan C ++ 11.
Roman L
11
Tapi kenapa disebut pop? itu cukup kontra-intuitif. Itu bisa diberi nama drop, dan kemudian jelas bagi semua orang bahwa itu tidak memunculkan elemen, tetapi malah menjatuhkannya ...
UeliDeSchwert
12
@utnapistim: operasi "pop" de-facto selalu mengambil elemen teratas dari tumpukan dan mengembalikannya. Ketika saya pertama kali menemukan tumpukan STL, saya terkejut, untuk sedikitnya, karena poptidak mengembalikan apa pun. Lihat misalnya di wikipedia .
Cris Luengo
34

Halaman yang telah Anda tautkan untuk menjawab pertanyaan Anda.

Mengutip seluruh bagian yang relevan:

Orang mungkin bertanya-tanya mengapa pop () mengembalikan void, bukan value_type. Yaitu, mengapa seseorang harus menggunakan front () dan pop () untuk memeriksa dan menghapus elemen di depan antrian, alih-alih menggabungkan keduanya dalam satu fungsi anggota? Sebenarnya, ada alasan bagus untuk desain ini. Jika pop () mengembalikan elemen depan, itu harus dikembalikan dengan nilai daripada dengan referensi: kembali dengan referensi akan membuat penunjuk yang menggantung. Mengembalikan menurut nilai, bagaimanapun, tidak efisien: ini melibatkan setidaknya satu panggilan konstruktor salinan yang redundan. Karena pop () tidak mungkin mengembalikan nilai sedemikian rupa sehingga menjadi efisien dan benar, lebih masuk akal untuk tidak mengembalikan nilai sama sekali dan meminta klien menggunakan front () untuk memeriksa nilai di depan antrian.

C ++ dirancang dengan mempertimbangkan efisiensi, melebihi jumlah baris kode yang harus ditulis oleh programmer.

BPF2010
sumber
16
Mungkin, tetapi alasan sebenarnya adalah bahwa tidak mungkin untuk mengimplementasikan versi aman pengecualian (dengan jaminan kuat) untuk versi popyang mengembalikan nilai.
James Kanze
5

pop tidak dapat mengembalikan referensi ke nilai yang dihapus, karena sedang dihapus dari struktur data, jadi apa referensi yang harus dirujuk? Itu bisa dikembalikan dengan nilai, tetapi bagaimana jika hasil pop tidak disimpan di mana pun? Kemudian waktu terbuang untuk menyalin nilai yang tidak perlu.

Neil Kirk
sumber
4
Alasan sebenarnya adalah keamanan pengecualian. Tidak ada cara yang aman untuk melakukan "transaksi" dengan tumpukan (baik elemen tetap berada di tumpukan, atau dikembalikan kepada Anda) jika poppengembalian dan tindakan pengembalian dapat mengakibatkan pengecualian. Itu jelas harus menghapus elemen sebelum mengembalikannya, dan kemudian jika sesuatu melempar elemen itu mungkin hilang secara permanen.
Jon
1
Apakah masalah ini masih berlaku dengan konstruktor pemindahan noexcept? (Tentu saja 0 salinan lebih efisien daripada dua gerakan, tapi itu bisa membuka pintu untuk pengecualian aman, efisien kombinasi depan + pop)
peppe
1
@peppe, Anda dapat mengembalikan menurut nilai jika Anda mengetahui value_typekonstruktor pemindahan nothrow, tetapi antarmuka antrian akan berbeda bergantung pada jenis objek yang Anda simpan di dalamnya, yang tidak akan membantu.
Jonathan Wakely
1
@peppe: Itu akan aman, tetapi stack adalah kelas pustaka generik dan oleh karena itu semakin diasumsikan tentang jenis elemen semakin kurang berguna.
Jon
Saya tahu STL tidak mengizinkannya, tetapi itu berarti seseorang dapat membuat kelas antriannya sendiri dengan blackjack dan ^ W ^ W ^ W dengan fitur ini :)
peppe
3

Dengan implementasi saat ini, ini valid:

int &result = myqueue.front();
std::cout << result;
myqueue.pop();

Jika pop mengembalikan referensi, seperti ini:

value_type& pop();

Kemudian kode berikut bisa macet, karena referensi tidak valid lagi:

int &result = myqueue.pop();
std::cout << result;

Di sisi lain, jika itu akan mengembalikan nilai secara langsung:

value_type pop();

Kemudian Anda perlu menyalin agar kode ini berfungsi, yang kurang efisien:

int result = myqueue.pop();
std::cout << result;
Jan Rüegg
sumber
1

Mulai dari C ++ 11 dimungkinkan untuk mengarsipkan perilaku yang diinginkan menggunakan semantik bergerak. Suka pop_and_move. Jadi copy konstruktor tidak akan dipanggil, dan kinerja akan bergantung pada konstruktor move saja.

Vyacheslav Putsenko
sumber
2
Tidak, semantik pemindahan tidak secara ajaib membuat poppengecualian menjadi aman.
LF
0

Anda benar-benar dapat melakukan ini:

std::cout << ' ' << myqueue.front();

Atau, jika Anda menginginkan nilai dalam variabel, gunakan referensi:

const auto &result = myqueue.front();
if (result > whatever) do_whatever();
std::cout << ' ' << result;

Di samping itu: kata 'lebih masuk akal' adalah bentuk subjektif dari 'kami melihat pola penggunaan dan menemukan lebih banyak kebutuhan untuk pemisahan'. (Yakinlah: bahasa C ++ tidak berkembang begitu saja ...)

xtofl
sumber
0

Saya pikir solusi terbaik adalah menambahkan sesuatu seperti

std::queue::pop_and_store(value_type& value);

dimana nilai akan menerima nilai yang muncul.

Keuntungannya adalah dapat diimplementasikan dengan menggunakan operator penugasan pindah, sementara menggunakan front + pop akan membuat salinan.

Masse Nicolas
sumber