Apa yang dimaksud dengan nilai, nilai, nilai, dan nilai?


1356

Dalam C ++ 03, ekspresi bisa berupa nilai p atau lvalue .

Di C ++ 11, sebuah ekspresi bisa menjadi:

  1. nilai
  2. lvalue
  3. nilai x
  4. nilai glv
  5. nilai awal

Dua kategori telah menjadi lima kategori.

  • Apa saja kategori ekspresi yang baru ini?
  • Bagaimana kategori baru ini berhubungan dengan kategori nilai dan nilai yang ada?
  • Apakah kategori rvalue dan lvalue dalam C ++ 0x sama dengan yang ada di C ++ 03?
  • Mengapa kategori baru ini dibutuhkan? Apakah para dewa WG21 hanya mencoba membingungkan kita hanya manusia biasa?

9
@Philip Potter: Dalam C ++ 03? Iya. Nilai dapat digunakan sebagai nilai karena ada konversi nilai standar ke nilai.
James McNellis

14
@ Tyler: "Jika Anda dapat menugaskannya, itu adalah nilai, jika tidak, itu adalah nilai." -> Salah, Anda dapat menetapkan untuk rvalues kelas: string("hello") = string("world").
fredoverflow

4
Perhatikan bahwa ini adalah kategori nilai. Ada lebih banyak properti yang bisa dimiliki oleh ekspresi. Ini termasuk bit-field (true / false), sementara (true / false) dan tipe (tipe itu).
Johannes Schaub - litb

30
Saya pikir tautan Fred di atas lebih baik daripada jawaban apa pun di sini. Tautannya sudah mati. Itu dipindahkan ke: stroustrup.com/terminology.pdf
R. Martinho Fernandes

74
di C ++ bahkan tipe Anda memiliki tipe
nielsbot

Jawaban:


634

Saya kira dokumen ini mungkin berfungsi sebagai pengantar yang tidak terlalu singkat: n3055

Seluruh pembantaian dimulai dengan gerakan semantik. Begitu kita memiliki ekspresi yang dapat dipindahkan dan tidak disalin, tiba-tiba mudah untuk memahami aturan yang menuntut perbedaan antara ekspresi yang dapat dipindahkan, dan ke arah mana.

Dari apa yang saya kira berdasarkan draft, perbedaan nilai r / l tetap sama, hanya dalam konteks memindahkan barang menjadi berantakan.

Apakah mereka dibutuhkan? Mungkin tidak jika kita ingin kehilangan fitur baru. Tetapi untuk memungkinkan optimalisasi yang lebih baik, kita mungkin harus merangkul mereka.

Mengutip n3055 :

  • Nilai l (yang disebut, secara historis, karena nilai bisa muncul di sisi kiri ekspresi penugasan) menunjuk fungsi atau objek. [Contoh: Jika Emerupakan ekspresi dari tipe pointer, maka *E adalah ekspresi lvalue yang merujuk pada objek atau fungsi ke E titik mana . Sebagai contoh lain, hasil memanggil fungsi yang tipe kembaliannya merupakan referensi nilai adalah nilai.]
  • Nilai x (nilai "eXpiring") juga merujuk ke objek, biasanya mendekati akhir masa pakainya (sehingga sumber dayanya dapat dipindahkan, misalnya). Nilai x adalah hasil dari jenis ekspresi tertentu yang melibatkan referensi nilai. [Contoh: Hasil memanggil fungsi yang tipe kembaliannya merupakan referensi nilai adalah nilai x.]
  • Sebuah glvalue ( “umum” lvalue) adalah lvalue atau Xvalue .
  • Nilai tambah (disebut, secara historis, karena nilai dapat muncul di sisi kanan ekspresi penugasan) adalah nilai x, objek sementara atau sub-objek daripadanya, atau nilai yang tidak terkait dengan objek.
  • Sebuah prvalue ( “murni” nilai p) adalah nilai p yang bukan Xvalue. [Contoh: Hasil memanggil fungsi yang tipe kembaliannya bukan referensi adalah nilai]

Dokumen yang dimaksud adalah referensi yang bagus untuk pertanyaan ini, karena menunjukkan perubahan tepat dalam standar yang telah terjadi sebagai hasil dari pengenalan nomenklatur baru.


Terima kasih, jawaban ini sangat membantu! Tetapi kompiler saya tidak setuju dengan contoh Anda untuk xvalues ​​dan prvalues; mereka justru sebaliknya. Mengembalikan dengan referensi nilai memberi saya nilai awal, dan mengembalikan dengan nilai memberi saya nilai x. Apakah Anda mencampuradukkannya, atau apakah test bed saya rusak? Saya mencoba ini dengan GCC 4.6.1, dentang (dari svn) dan MSVC, dan mereka semua menunjukkan perilaku yang sama.
Kim Gräsman

Ups, saya hanya mengikuti tautan dan memperhatikan bahwa contohnya ada di sumbernya. Saya akan pergi mencari salinan standar saya dan memeriksa apa yang tertulis ...
Kim Gräsman

4
Saya menggunakan makro dari sini untuk menguji berbagai ekspresi: stackoverflow.com/a/6114546/96963 Bisa jadi mereka salah mendiagnosis sesuatu.
Kim Gräsman

1
Menambahkan xvalue bukan untuk memindahkan semantik. Hanya dengan kedua lvalue dan rvalue, semantik bergerak, rujukan maju sempurna dan referensi nilai masih bekerja dengan baik. Saya pikir xvalue hanya untuk operator decltype: jika ekspresi operand adalah xvalue, maka decltype memberikan jenis referensi rvalue.
ligan

1
@MuhamedCicak "Setiap ekspresi adalah nilai atau nilai": itu benar; dan standar (atau dokumen n3055) tidak mengatakan itu salah. Alasan kalimat ini dicoret adalah karena Anda melihat perubahan antara dua versi dokumen. Kalimat itu dihapus karena menjadi berlebihan setelah penjelasan yang lebih tepat ditambahkan.
maks

337

Apa saja kategori ekspresi yang baru ini?

The FCD (n3092) memiliki deskripsi yang sangat baik:

- Nilai l (disebut demikian, secara historis, karena nilai dapat muncul di sisi kiri ekspresi penugasan) menunjuk fungsi atau objek. [Contoh: Jika E adalah ekspresi dari tipe pointer, maka * E adalah ekspresi lvalue yang merujuk pada objek atau fungsi yang ditunjuk oleh E. Sebagai contoh lain, hasil memanggil fungsi yang tipe kembaliannya adalah referensi lvalue adalah lvalue. —Kirim contoh]

- Nilai x (nilai "eXpiring") juga merujuk ke objek, biasanya mendekati akhir masa pakainya (sehingga sumber dayanya dapat dipindahkan, misalnya). Nilai x adalah hasil dari jenis ekspresi tertentu yang melibatkan referensi nilai (8.3.2). [Contoh: Hasil memanggil fungsi yang tipe kembaliannya merupakan referensi nilai adalah nilai x. —Kirim contoh]

- Glvalue (lvalue "umum") adalah lvalue atau xvalue.

- Nilai sebuah (disebut, secara historis, karena nilai dapat muncul di sisi kanan ekspresi penugasan) adalah nilai x, objek sementara (12.2) atau sub-objek daripadanya, atau nilai yang tidak terkait dengan objek.

- Nilai awal (“murni” nilai) adalah nilai yang bukan nilai x. [Contoh: Hasil memanggil fungsi yang tipe kembaliannya bukan referensi adalah nilai awal. Nilai literal seperti 12, 7.3e5, atau true juga merupakan nilai awal. —Kirim contoh]

Setiap ungkapan termasuk salah satu klasifikasi mendasar dalam taksonomi ini: nilai, nilai, atau nilai. Properti ekspresi ini disebut kategori nilainya. [Catatan: Diskusi masing-masing operator bawaan pada Klausa 5 menunjukkan kategori nilai yang dihasilkannya dan kategori nilai operan yang diharapkannya. Sebagai contoh, operator penugasan bawaan berharap bahwa operan kiri adalah lvalue dan bahwa operan kanan adalah prvalue dan menghasilkan lvalue sebagai hasilnya. Operator yang ditentukan pengguna adalah fungsi, dan kategori nilai yang mereka harapkan dan hasilkan ditentukan oleh parameter dan tipe pengembaliannya. —Menghapus

Saya sarankan Anda membaca seluruh bagian 3.10 Nilai dan nilai meskipun.

Bagaimana kategori baru ini berhubungan dengan kategori nilai dan nilai yang ada?

Lagi:

Taksonomi

Apakah kategori rvalue dan lvalue dalam C ++ 0x sama dengan yang ada di C ++ 03?

Semantik nilai-nilai telah berkembang terutama dengan diperkenalkannya semantik bergerak.

Mengapa kategori baru ini dibutuhkan?

Sehingga pemindahan konstruksi / penugasan dapat didefinisikan dan didukung.


54
Saya suka diagram di sini. Saya pikir mungkin berguna untuk memulai jawaban dengan "Setiap ungkapan milik tepat salah satu klasifikasi mendasar dalam taksonomi ini: lvalue, xvalue, atau prvalue." Maka mudah untuk menggunakan diagram untuk menunjukkan ketiga kelas dasar digabungkan untuk membuat glvalue dan rvalue.
Aaron McDaid

2
"is glvalue" setara dengan "is not prvalue", dan "is rvalue" adalah setara dengan "not lvalue".
Vladimir Reshetnikov

2
Yang ini paling membantu saya: bajamircea.github.io/assets/2016-04-07-move-forward/… (diagram Venn untuk kategori nilai)
John P

1
@AaronMcDaid Hai, pertanyaan singkat jika Anda / seseorang dapat menjawab ... Mengapa tidak menyebutkan glvaluesebagai lvaluedan lvaluesebagai plvalue, agar konsisten?
Vijay Chavda

184

Saya akan mulai dengan pertanyaan terakhir Anda:

Mengapa kategori baru ini dibutuhkan?

Standar C ++ berisi banyak aturan yang berhubungan dengan kategori nilai ekspresi. Beberapa aturan membuat perbedaan antara nilai dan nilai. Misalnya, ketika datang ke resolusi kelebihan beban. Aturan lain membuat perbedaan antara glvalue dan prvalue. Misalnya, Anda dapat memiliki glvalue dengan tipe yang tidak lengkap atau abstrak tetapi tidak ada nilai dengan tipe yang tidak lengkap atau abstrak. Sebelum kita memiliki terminologi ini, aturan yang benar-benar perlu membedakan antara glvalue / prvalue disebut lvalue / rvalue dan keduanya salah secara tidak sengaja atau mengandung banyak penjelasan dan pengecualian pada aturan a la "... kecuali jika nilainya disebabkan karena tidak disebutkan namanya. referensi nilai ... ". Jadi, sepertinya ide yang bagus untuk hanya memberikan konsep nilai-nilai dan nilai-nilai mereka sendiri.

Apa saja kategori ekspresi yang baru ini? Bagaimana kategori baru ini berhubungan dengan kategori nilai dan nilai yang ada?

Kami masih memiliki istilah lvalue dan rvalue yang kompatibel dengan C ++ 98. Kami baru saja membagi nilai-nilai menjadi dua subkelompok, nilai-nilai x dan nilai-nilai, dan kami menyebut nilai-nilai dan nilai-nilai x sebagai nilai-rendah. Xvalues ​​adalah jenis baru dari kategori nilai untuk referensi nilai tak bernama. Setiap ekspresi adalah salah satu dari ketiganya: lvalue, xvalue, prvalue. Diagram Venn akan terlihat seperti ini:

    ______ ______
   /      X      \
  /      / \      \
 |   l  | x |  pr  |
  \      \ /      /
   \______X______/
       gl    r

Contoh dengan fungsi:

int   prvalue();
int&  lvalue();
int&& xvalue();

Tetapi juga jangan lupa bahwa referensi nilai yang dinamai adalah nilai:

void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}

165

Mengapa kategori baru ini dibutuhkan? Apakah para dewa WG21 hanya mencoba membingungkan kita hanya manusia biasa?

Saya tidak merasa bahwa jawaban yang lain (baik meskipun banyak dari mereka) benar-benar menangkap jawaban untuk pertanyaan khusus ini. Ya, kategori ini dan semacamnya ada untuk memungkinkan semantik bergerak, tetapi kompleksitas ada karena satu alasan. Ini adalah aturan yang tidak berlaku untuk memindahkan barang di C ++ 11:

Anda hanya akan bergerak jika tidak aman untuk melakukannya.

Itulah sebabnya kategori-kategori ini ada: untuk dapat berbicara tentang nilai-nilai yang aman untuk dipindahkan darinya, dan untuk berbicara tentang nilai-nilai yang tidak aman.

Dalam versi referensi r-value paling awal, perpindahan terjadi dengan mudah. Terlalu mudah Cukup mudah sehingga ada banyak potensi untuk memindahkan barang secara implisit ketika pengguna tidak benar-benar bermaksud.

Berikut adalah keadaan di mana aman untuk memindahkan sesuatu:

  1. Ketika itu sementara atau subobjeknya. (prvalue)
  2. Ketika pengguna secara eksplisit mengatakan untuk memindahkannya .

Jika kamu melakukan ini:

SomeType &&Func() { ... }

SomeType &&val = Func();
SomeType otherVal{val};

Apa fungsinya? Dalam versi yang lebih lama dari spec, sebelum 5 nilai masuk, ini akan memancing langkah. Tentu saja. Anda meneruskan referensi nilai ke konstruktor, dan karenanya mengikat ke konstruktor yang mengambil referensi nilai. Itu sudah jelas.

Hanya ada satu masalah dengan ini; Anda tidak meminta untuk memindahkannya. Oh, Anda mungkin mengatakan bahwa itu &&seharusnya petunjuk, tapi itu tidak mengubah fakta bahwa itu melanggar aturan. valbukan sementara karena temporer tidak memiliki nama. Anda mungkin telah memperpanjang masa pakai sementara, tetapi itu berarti itu tidak sementara ; itu seperti variabel stack lainnya.

Jika ini bukan sementara, dan Anda tidak meminta untuk memindahkannya, maka pindah itu salah.

Solusi yang jelas adalah membuat valnilai. Ini berarti bahwa Anda tidak dapat bergerak darinya. Baiklah; itu dinamai, jadi itu adalah nilai.

Setelah Anda melakukan itu, Anda tidak bisa lagi mengatakan itu SomeType&&berarti hal yang sama di mana pun. Anda sekarang telah membuat perbedaan antara referensi nilai yang disebutkan dan referensi nilai yang tidak disebutkan namanya. Yah, referensi nilai yang dinamai adalah nilai; itulah solusi kami di atas. Jadi apa yang kita sebut referensi nilai tak bernama (nilai pengembalian dari Funcatas)?

Ini bukan lvalue, karena Anda tidak bisa bergerak dari lvalue. Dan kita harus bisa bergerak dengan mengembalikan a &&; bagaimana lagi yang bisa Anda katakan secara eksplisit untuk memindahkan sesuatu? Lagipula itulah yang std::movekembali. Itu bukan nilai (gaya lama), karena bisa di sisi kiri persamaan (hal-hal sebenarnya sedikit lebih rumit, lihat pertanyaan ini dan komentar di bawah). Itu bukan nilai atau nilai; itu hal baru.

Apa yang kami miliki adalah nilai yang dapat Anda perlakukan sebagai lvalue, kecuali bahwa nilai itu secara implisit dapat dipindahkan. Kami menyebutnya nilai x.

Perhatikan bahwa xvalues ​​adalah apa yang membuat kami memperoleh dua kategori nilai lainnya:

  • Prvalue benar-benar hanya nama baru untuk tipe nilai sebelumnya, yaitu nilai yang bukan nilai x.

  • Glvalues ​​adalah gabungan dari nilai x dan nilai dalam satu kelompok, karena mereka memiliki banyak kesamaan properti.

Jadi sungguh, itu semua bermuara pada nilai-nilai x dan kebutuhan untuk membatasi pergerakan hanya ke tempat-tempat tertentu saja. Tempat-tempat itu ditentukan oleh kategori nilai; prvalues ​​adalah gerakan implisit, dan xvalues ​​adalah gerakan eksplisit ( std::movemengembalikan nilai x).


11
@ Thomas: Ini contohnya; tidak masalah bagaimana ia menciptakan nilai balik. Yang penting adalah bahwa ia mengembalikan a &&.
Nicol Bolas

1
Catatan: nilai bisa berada di sisi kiri persamaan, juga - seperti pada X foo(); foo() = X;... Untuk alasan mendasar ini, saya tidak bisa mengikuti jawaban yang sangat baik sampai akhir, karena Anda benar-benar hanya membuat perbedaan antara xvalue baru, dan prvalue gaya lama, berdasarkan pada fakta bahwa itu bisa di lhs.
Dan Nissenbaum

1
Xmenjadi kelas; X foo();menjadi deklarasi fungsi, dan foo() = X();menjadi garis kode. (Saya meninggalkan set kedua tanda kurung di foo() = X();dalam komentar saya di atas.) Untuk pertanyaan yang baru saja saya posting dengan penggunaan ini disorot, lihat stackoverflow.com/questions/15482508/…
Dan Nissenbaum

1
@DanNissenbaum "xvalue tidak bisa di sebelah kiri ekspresi tugas" - mengapa tidak? Lihat ideone.com/wyrxiT
Mikhail

1
Jawaban yang mencerahkan. Ini tidak diragukan lagi jawaban terbaik di sini. Itu memberi saya alasan untuk memperkenalkan kategori nilai baru dan apa yang terjadi sebelumnya.
Nikos

136

IMHO, penjelasan terbaik tentang artinya memberi kami Stroustrup + memperhitungkan contoh akun Dániel Sándor dan Mohan :

Stroustrup:

Sekarang saya sangat khawatir. Jelas kami menuju jalan buntu atau kekacauan atau keduanya. Saya menghabiskan waktu makan siang melakukan analisis untuk melihat properti (nilai) mana yang independen. Hanya ada dua properti independen:

  • has identity - yaitu dan alamat, penunjuk, pengguna dapat menentukan apakah dua salinan identik, dll.
  • can be moved from - yaitu kita diizinkan meninggalkan sumber "salinan" di beberapa negara yang tidak ditentukan, tetapi valid

Ini membawa saya pada kesimpulan bahwa ada tiga jenis nilai (menggunakan trik notasi regex menggunakan huruf kapital untuk menunjukkan negatif - saya sedang terburu-buru):

  • iM: memiliki identitas dan tidak dapat dipindahkan dari
  • im: memiliki identitas dan dapat dipindahkan dari (mis. hasil casting nilai ke referensi nilai)
  • Im: tidak memiliki identitas dan dapat dipindahkan dari.

    Kemungkinan keempat IM,, (tidak memiliki identitas dan tidak dapat dipindahkan) tidak berguna dalam C++(atau, saya pikir) dalam bahasa lain.

Selain tiga klasifikasi mendasar nilai-nilai ini, kami memiliki dua generalisasi jelas yang sesuai dengan dua sifat independen:

  • i: memiliki identitas
  • m: dapat dipindahkan dari

Ini membuat saya meletakkan diagram ini di papan tulis: masukkan deskripsi gambar di sini

Penamaan

Saya mengamati bahwa kami hanya memiliki kebebasan terbatas untuk menyebutkan: Dua poin di sebelah kiri (berlabel iMdan i) adalah apa yang disebut oleh orang-orang dengan formalitas lebih dan kurang lvaluesdan dua poin di sebelah kanan (berlabel mdan Im) adalah apa yang orang-orang dengan formalitas lebih atau kurang. telah memanggil rvalues. Ini harus tercermin dalam penamaan kami. Yaitu, "kaki" kiri Wharus memiliki nama yang terkait lvaluedan "kaki" kanan Wharus memiliki nama yang berkaitan dengan rvalue.Saya perhatikan bahwa seluruh diskusi / masalah ini muncul dari pengenalan referensi nilai dan pindahkan semantik. Gagasan-gagasan ini sama sekali tidak ada di dunia Strachey yang terdiri dari adil rvaluesdan lvalues. Seseorang mengamati ide-ide itu

  • Setiap valueadalah salah satu lvalueatau saturvalue
  • An lvaluebukan an rvaluedan rvaluebukan anlvalue

sangat tertanam dalam kesadaran kita, sifat yang sangat berguna, dan jejak dikotomi ini dapat ditemukan di seluruh rancangan standar. Kami semua sepakat bahwa kami harus melestarikan properti itu (dan membuatnya akurat). Ini semakin membatasi pilihan penamaan kami. Saya mengamati bahwa kata-kata standar perpustakaan menggunakan rvalueberarti m(generalisasi), sehingga untuk menjaga harapan dan teks dari perpustakaan standar titik bawah kanan dari Wharus dinamai rvalue.

Hal ini menyebabkan diskusi penamaan yang terfokus. Pertama, kita perlu memutuskan lvalue.Haruskah lvalueartinya iMatau generalisasi i? Dipimpin oleh Doug Gregor, kami membuat daftar tempat-tempat dalam bahasa inti dengan kata-kata di mana kata lvalueitu memenuhi syarat untuk berarti satu atau yang lain. Daftar dibuat dan dalam banyak kasus dan dalam teks paling rumit / rapuh lvaluesaat ini berarti iM. Ini adalah makna klasik dari nilai karena "di masa lalu" tidak ada yang bergerak; moveadalah gagasan baru dalam bahasa Indonesia C++0x. Juga, memberi nama titik topleft W lvaluememberi kita properti bahwa setiap nilai adalah lvalueataurvalue , tetapi tidak keduanya.

Jadi, titik kiri atas Wis lvaluedan titik kanan bawah adalah rvalue.Apa yang membuat titik kiri bawah dan kanan atas? Titik kiri bawah adalah generalisasi dari nilai klasik, memungkinkan untuk bergerak. Jadi itu adalah generalized lvalue.Kami menamakannya glvalue.Anda dapat berdalih tentang singkatan, tapi (saya pikir) tidak dengan logika. Kami berasumsi bahwa penggunaan yang serius generalized lvalue bagaimanapun juga akan disingkat, jadi sebaiknya kita segera melakukannya (atau berisiko kebingungan). Titik kanan atas W kurang umum daripada kanan bawah (sekarang, seperti biasa, disebut rvalue). Titik itu mewakili gagasan murni asli dari objek yang dapat Anda pindahkan karena tidak dapat dirujuk lagi (kecuali oleh destruktor). Saya suka frasa yang disingkatspecialized rvalue berbeda dengan generalized lvaluetetapipure rvalueprvaluemenang (dan mungkin memang demikian). Jadi, kaki kiri W adalah lvaluedan glvaluedan kaki kanan adalah prvaluedanrvalue. Kebetulan, setiap nilai adalah nilai rendah atau nilai awal, tetapi tidak keduanya.

Ini daun bagian atas tengah W: im; yaitu, nilai-nilai yang memiliki identitas dan dapat dipindahkan. Kami benar-benar tidak memiliki apa pun yang menuntun kami ke nama baik untuk binatang esoteris itu. Mereka penting bagi orang yang bekerja dengan teks standar (draft), tetapi tidak mungkin menjadi nama rumah tangga. Kami tidak menemukan kendala nyata pada penamaan untuk memandu kami, jadi kami memilih 'x' untuk pusat, tidak diketahui, aneh, hanya xpert, atau bahkan x-rated.

Steve memamerkan produk akhir


14
ya, lebih baik membaca proposal asli dan diskusi dari komite C ++, daripada standar, jika Anda ingin memahami apa artinya: D
Ivan Kush

8
Literal tidak memiliki identitas dan tidak dapat dipindahkan; mereka tetap berguna.
DrPizza

Saya hanya ingin mengklarifikasi sesuatu. int && f () {return 1; } dan MyClass && g () {return MyClass (); } kembalikan xvalue, kan? Lalu di mana saya dapat menemukan identitas ekspresi f (); dan "g ();"? Mereka memiliki identitas, karena ada ungkapan lain dalam pernyataan kembali, yang merujuk pada objek yang sama dengan yang mereka maksud - apakah saya memahaminya dengan benar?
Dániel Sándor

6
@DrPizza Menurut standar: string literal adalah lvalues, semua literal lainnya adalah prvalues. Sebenarnya Anda bisa membuat argumen untuk mengatakan non-string literal tidak bisa digerakkan, tapi itu bukan standar yang ditulis.
Brian Vandenberg

59

PENGANTAR

ISOC ++ 11 (secara resmi ISO / IEC 14882: 2011) adalah versi terbaru dari standar bahasa pemrograman C ++. Ini berisi beberapa fitur baru, dan konsep, misalnya:

  • referensi nilai
  • xvalue, glvalue, prvalue kategori nilai ekspresi
  • pindahkan semantik

Jika kami ingin memahami konsep kategori nilai ekspresi baru, kami harus mengetahui bahwa ada referensi nilai dan nilai. Lebih baik untuk mengetahui nilai dapat diteruskan ke referensi nilai non-const.

int& r_i=7; // compile error
int&& rr_i=7; // OK

Kita dapat memperoleh beberapa intuisi dari konsep kategori nilai jika kita mengutip sub-bagian berjudul Lvalues ​​dan rvalues ​​dari draft kerja N3337 (draft paling mirip dengan standar ISOC ++ 11 yang diterbitkan).

3.10 Nilai dan nilai [basic.lval]

1 Ekspresi dikategorikan menurut taksonomi pada Gambar 1.

  • Nilai l (disebut demikian, secara historis, karena nilai bisa muncul di sebelah kiri ekspresi penugasan) menunjuk fungsi atau objek. [Contoh: Jika E adalah ekspresi dari tipe pointer, maka * E adalah ekspresi lvalue yang merujuk pada objek atau fungsi yang ditunjuk oleh E. Sebagai contoh lain, hasil memanggil fungsi yang tipe kembaliannya adalah referensi lvalue adalah lvalue. —Kirim contoh]
  • Nilai x (nilai "eXpiring") juga merujuk ke objek, biasanya mendekati akhir masa pakainya (sehingga sumber dayanya dapat dipindahkan, misalnya). Nilai x adalah hasil dari jenis ekspresi tertentu yang melibatkan referensi nilai (8.3.2). [Contoh: Hasil memanggil fungsi yang tipe kembaliannya merupakan referensi nilai adalah nilai x. —Kirim contoh]
  • Glvalue (lvalue "umum") adalah lvalue atau xvalue.
  • Nilai sebuah (disebut, secara historis, karena nilai bisa muncul di sisi kanan ekspresi penugasan) adalah nilai x,
    objek sementara (12.2) atau sub-objek daripadanya, atau nilai yang tidak
    terkait dengan objek.
  • Prvalue (“pure” rvalue) adalah rvalue yang bukan xvalue. [Contoh: Hasil memanggil fungsi yang tipe kembaliannya bukan
    referensi adalah nilai awal. Nilai literal seperti 12, 7.3e5, atau
    true juga merupakan nilai awal. —Kirim contoh]

Setiap ungkapan termasuk salah satu klasifikasi mendasar dalam taksonomi ini: nilai, nilai, atau nilai. Properti ekspresi ini disebut kategori nilainya.

Tetapi saya tidak begitu yakin tentang ayat ini cukup untuk memahami konsep-konsep dengan jelas, karena "biasanya" tidak benar-benar umum, "mendekati akhir masa hidupnya" tidak benar-benar konkret, "melibatkan referensi nilai" tidak benar-benar jelas, dan "Contoh: Hasil memanggil fungsi yang tipe kembaliannya merupakan referensi nilai adalah nilai x." terdengar seperti ular menggigit ekornya.

KATEGORI NILAI PRIMER

Setiap ekspresi memiliki tepat satu kategori nilai primer. Kategori-kategori nilai ini adalah kategori nilai, nilai, dan kategori nilai awal.

nilai-nilai

Ekspresi E milik kategori nilai jika dan hanya jika E merujuk ke entitas yang SUDAH memiliki identitas (alamat, nama atau alias) yang membuatnya dapat diakses di luar E.

#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.  

    i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression i in this row refers to.

    int* p_i=new int(7);
    *p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
    *p_i; // ... as the entity the expression *p_i in this row refers to.

    const int& r_I=7;
    r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
    r_I; // ... as the entity the expression r_I in this row refers to.

    f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression f() in this row refers to.

    return 0;
}

nilai x

Ekspresi E milik kategori xvalue jika dan hanya jika itu

- hasil memanggil suatu fungsi, baik secara implisit atau eksplisit, yang tipe pengembaliannya merupakan rvalue referensi ke tipe objek yang dikembalikan, atau

int&& f(){
    return 3;
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.

    return 0;
}

- pemeran untuk referensi nilai ke tipe objek, atau

int main()
{
    static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type.
    std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7).

    return 0;
}

- ekspresi akses anggota kelas yang menunjuk anggota data non-statis dari tipe non-referensi di mana ekspresi objek adalah xvalue, atau

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.

    return 0;
}

- ekspresi pointer-ke-anggota di mana operan pertama adalah nilai x dan operan kedua adalah pointer ke anggota data.

Perhatikan bahwa efek dari aturan di atas adalah bahwa rujukan nilai yang dinamai objek diperlakukan sebagai lvalues ​​dan referensi rvalue yang tidak disebutkan namanya untuk objek diperlakukan sebagai nilai x; referensi nilai untuk fungsi diperlakukan sebagai nilai apakah namanya atau tidak.

#include <functional>

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object.
    As&& rr_a=As();
    rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object.
    std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function.

    return 0;
}

nilai-nilai

Ekspresi E termasuk dalam kategori nilai awal jika dan hanya jika E tidak termasuk dalam kategori nilai rendah atau juga untuk kategori harga rendah.

struct As
{
    void f(){
        this; // The expression this is a prvalue expression. Note, that the expression this is not a variable.
    }
};

As f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category.

    return 0;
}

KATEGORI NILAI CAMPURAN

Ada dua kategori nilai campuran lebih lanjut yang penting. Kategori nilai ini adalah kategori rvalue dan glvalue.

nilai-nilai

Ekspresi E milik kategori nilai jika dan hanya jika E milik kategori nilai, atau ke kategori nilai awal.

Perhatikan bahwa definisi ini berarti bahwa ekspresi E milik kategori nilai jika dan hanya jika E mengacu pada entitas yang tidak memiliki identitas yang membuatnya dapat diakses di luar E YET.

nilai-nilai

Ekspresi E milik kategori glvalue jika dan hanya jika E milik kategori lvalue, atau ke kategori xvalue.

ATURAN PRAKTIS

Scott Meyer telah menerbitkan aturan praktis yang sangat berguna untuk membedakan nilai-nilai dari nilai-nilai.

  • Jika Anda dapat mengambil alamat suatu ekspresi, ekspresi tersebut adalah nilai.
  • Jika jenis ekspresi adalah referensi nilai (misalnya, T & atau const T &, dll.), Ekspresi itu adalah nilai.
  • Kalau tidak, ekspresi adalah nilai. Secara konseptual (dan biasanya juga sebenarnya), nilai-nilai sesuai dengan objek sementara, seperti yang dikembalikan dari fungsi atau dibuat melalui konversi tipe implisit. Sebagian besar nilai literal (misalnya, 10 dan 5.3) juga merupakan nilai.

3
Semua contoh untuk nilai-nilai dan semua contoh untuk nilai-nilai juga merupakan contoh untuk nilai-nilai juga. Terima kasih telah mengedit!
Dániel Sándor

1
Kamu benar. Tiga kategori nilai primer sudah cukup. Nilai juga tidak perlu. Saya pikir rvalue dan glvalue berada dalam standar untuk kenyamanan.
Dániel Sándor

1
Memiliki waktu yang sulit untuk memahami struct As{void f(){this;}}para thisvariabel prvalue a. Saya pikir thisseharusnya menjadi nilai. Sampai standar 9.3.2 mengatakan: Di dalam fungsi anggota non-statis (9.3), kata kunci ini adalah ekspresi nilai awal.
r0ng

3
@ r0ng thisadalah nilai awal tetapi *thisnilai rendah
Xeverous

1
"www" tidak selalu memiliki alamat yang sama. Ini adalah nilai karena merupakan array .
wally

35

Kategori C ++ 03 terlalu terbatas untuk menangkap pengenalan referensi nilai dengan benar ke atribut ekspresi.

Dengan diperkenalkannya mereka, dikatakan bahwa referensi nilai yang tidak disebutkan namanya mengevaluasi ke suatu nilai, sehingga resolusi kelebihan beban akan lebih memilih ikatan referensi nilai, yang akan membuatnya memilih konstruktor bergerak daripada konstruktor salinan. Tetapi ditemukan bahwa ini menyebabkan masalah di sekitar, misalnya dengan Jenis Dinamis dan dengan kualifikasi.

Untuk menunjukkan ini, pertimbangkan

int const&& f();

int main() {
  int &&i = f(); // disgusting!
}

Pada konsep pra-xvalue, ini diizinkan, karena dalam C ++ 03, nilai jenis non-kelas tidak pernah memenuhi syarat cv. Tapi ini dimaksudkan yang constberlaku dalam kasus referensi-nilai, karena di sini kita lakukan merujuk ke objek (= memori!), Dan menjatuhkan const dari rvalues non-kelas terutama untuk alasan bahwa tidak ada objek di sekitar.

Masalah untuk tipe dinamis bersifat serupa. Di C ++ 03, nilai dari tipe kelas memiliki tipe dinamis yang dikenal - itu adalah tipe statis dari ekspresi itu. Karena untuk mendapatkannya dengan cara lain, Anda perlu referensi atau referensi, yang mengevaluasi ke nilai. Itu tidak benar dengan referensi nilai yang tidak disebutkan namanya, namun mereka dapat menunjukkan perilaku polimorfik. Jadi untuk menyelesaikannya,

  • referensi nilai tidak bernama menjadi nilai- x . Mereka dapat dikualifikasi dan berpotensi memiliki tipe dinamis yang berbeda. Mereka memang, seperti yang dimaksudkan, lebih suka referensi nilai saat overloading, dan tidak akan mengikat ke nilai referensi non-konstan.

  • Apa yang sebelumnya adalah nilai p (literal, benda yang diciptakan oleh gips untuk jenis non-reference) sekarang menjadi prvalue . Mereka memiliki preferensi yang sama dengan nilai x selama overloading.

  • Apa yang sebelumnya adalah nilai tetap nilai.

Dan dua pengelompokan dilakukan untuk menangkap yang dapat dikualifikasi dan dapat memiliki tipe dinamis yang berbeda ( glvalues ) dan yang mana kelebihan beban lebih memilih pengikatan referensi nilai-nilai ( rvalues ).


1
jawabannya jelas masuk akal. xvalue hanyalah rvalue yang dapat dikualifikasikan secara cv dan dinamis!
ligan

26

Saya telah berjuang dengan ini untuk waktu yang lama, sampai saya menemukan penjelasan cppreference.com dari kategori nilai .

Ini sebenarnya agak sederhana, tetapi saya menemukan bahwa itu sering dijelaskan dengan cara yang sulit untuk dihafal. Di sini dijelaskan dengan sangat skematis. Saya akan mengutip beberapa bagian halaman:

Kategori utama

Kategori nilai primer sesuai dengan dua properti ekspresi:

  • memiliki identitas : dimungkinkan untuk menentukan apakah ekspresi merujuk pada entitas yang sama dengan ekspresi lain, seperti dengan membandingkan alamat objek atau fungsi yang mereka identifikasi (diperoleh secara langsung atau tidak langsung);

  • dapat dipindahkan dari : memindahkan konstruktor, memindahkan penugasan operator, atau fungsi berlebih lainnya yang mengimplementasikan pemindahan semantik dapat mengikat ekspresi.

Ekspresi yang:

  • memiliki identitas dan tidak dapat dipindahkan dari disebut ekspresi nilai ;
  • memiliki identitas dan dapat dipindahkan dari yang disebut ekspresi xvalue ;
  • tidak memiliki identitas dan dapat dipindahkan dari yang disebut ekspresi prvalue ;
  • tidak memiliki identitas dan tidak dapat dipindahkan dari tidak digunakan.

lvalue

Ekspresi lvalue ("nilai kiri") adalah ekspresi yang memiliki identitas dan tidak dapat dipindahkan .

rvalue (hingga C ++ 11), prvalue (sejak C ++ 11)

Ekspresi prvalue ("pure rvalue") adalah ekspresi yang tidak memiliki identitas dan dapat dipindahkan .

nilai x

Ekspresi xvalue ("nilai kedaluwarsa") adalah ekspresi yang memiliki identitas dan dapat dipindahkan dari .

nilai glv

Ekspresi glvalue ("lvalue umum") adalah ekspresi yang bernilai lvalue atau xvalue. Ia memiliki identitas . Mungkin atau mungkin tidak dipindahkan dari.

rvalue (sejak C ++ 11)

Ekspresi rvalue ("nilai kanan") adalah ekspresi yang merupakan prvalue atau xvalue. Itu bisa dipindahkan dari . Mungkin atau mungkin tidak memiliki identitas.


1
Dalam beberapa buku, nilai-nilai x terbukti memiliki x mereka berasal dari "pakar" atau "luar biasa"
noɥʇʎԀʎzɐɹƆ

Dan yang lebih penting, daftar contoh lengkap mereka.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

19

Bagaimana kategori baru ini berhubungan dengan kategori nilai dan nilai yang ada?

Nilai C ++ 03 masih merupakan nilai C ++ 11, sedangkan nilai C ++ 03 disebut nilai awal dalam C ++ 11.


14

Satu tambahan untuk jawaban bagus di atas, pada titik yang membingungkan saya bahkan setelah saya membaca Stroustrup dan berpikir saya memahami perbedaan nilai / nilai. Ketika kamu melihat

int&& a = 3,

sangat menggoda untuk membaca int&&sebagai tipe dan menyimpulkan bahwa itu aadalah nilai. Ini bukan:

int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles

amemiliki nama dan ipso facto merupakan nilai tambah. Jangan menganggapnya &&sebagai bagian dari tipe a; itu hanya sesuatu yang memberi tahu Anda apaa yang diizinkan untuk diikat.

Ini penting terutama untuk T&&mengetikkan argumen dalam konstruktor. Jika kamu menulis

Foo::Foo(T&& _t) : t{_t} {}

Anda akan menyalin _tke dalam t. Kamu butuh

Foo::Foo(T&& _t) : t{std::move(_t)} {}jika ingin pindah. Apakah itu kompiler saya memperingatkan saya ketika saya meninggalkan move!


1
Saya pikir jawaban ini bisa diklarifikasi. "Apa ayang diizinkan untuk diikat": Tentu, tetapi pada baris 2 & 3 variabel Anda adalah c & b, dan itu bukan yang mengikat, dan jenisnya atidak relevan di sini, bukan? Garis akan sama jika adideklarasikan int a. Perbedaan utama sebenarnya di sini adalah bahwa pada baris 1 a tidak harus constterikat ke 3.
Felix Dombek

12

Karena jawaban sebelumnya membahas teori di balik kategori nilai, ada satu hal lain yang ingin saya tambahkan: Anda dapat benar-benar bermain dengannya dan mengujinya.

Untuk beberapa eksperimen langsung dengan kategori nilai, Anda dapat menggunakan penentu jenis deklarasi . Perilakunya secara eksplisit membedakan antara tiga kategori nilai primer (xvalue, lvalue, dan prvalue).

Menggunakan preprocessor menyelamatkan kita dari pengetikan ...

Kategori utama:

#define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value
#define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value
#define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value

Kategori campuran:

#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))
#define IS_RVALUE(X) (IS_PRVALUE(X) || IS_XVALUE(X))

Sekarang kita dapat mereproduksi (hampir) semua contoh dari cppreference pada kategori nilai .

Berikut adalah beberapa contoh dengan C ++ 17 (untuk terse static_assert):

void doesNothing(){}
struct S
{
    int x{0};
};
int x = 1;
int y = 2;
S s;

static_assert(IS_LVALUE(x));
static_assert(IS_LVALUE(x+=y));
static_assert(IS_LVALUE("Hello world!"));
static_assert(IS_LVALUE(++x));

static_assert(IS_PRVALUE(1));
static_assert(IS_PRVALUE(x++));
static_assert(IS_PRVALUE(static_cast<double>(x)));
static_assert(IS_PRVALUE(std::string{}));
static_assert(IS_PRVALUE(throw std::exception()));
static_assert(IS_PRVALUE(doesNothing()));

static_assert(IS_XVALUE(std::move(s)));
// The next one doesn't work in gcc 8.2 but in gcc 9.1. Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 

Kategori campuran agak membosankan setelah Anda menemukan kategori utama.

Untuk beberapa contoh lainnya (dan percobaan), lihat tautan berikut tentang kompiler explorer . Namun, jangan repot-repot membaca majelis. Saya menambahkan banyak kompiler hanya untuk memastikan itu bekerja di semua kompiler umum.


Saya pikir #define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)seharusnya benar-benar #define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))melihat apa yang terjadi jika kalian &&berdua IS_GLVALUE.
Gabriel Devillers
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.