Apa itu "referensi nilai untuk * ini"?

238

Datang di proposal yang disebut "referensi nilai untuk * ini" di halaman status C ++ 11 dentang .

Saya sudah membaca sedikit tentang rvalue referensi dan memahaminya, tapi saya rasa saya tidak tahu tentang ini. Saya juga tidak dapat menemukan banyak sumber daya di web menggunakan istilah tersebut.

Ada tautan ke makalah proposal di halaman: N2439 (Memperluas semantik ke * ini), tapi saya juga tidak mendapatkan banyak contoh dari sana.

Tentang apa fitur ini?

ryaner
sumber

Jawaban:

293

Pertama, "ref-kualifikasi untuk * ini" hanyalah "pernyataan pemasaran". Jenis *thistidak pernah berubah, lihat bagian bawah posting ini. Akan lebih mudah untuk memahaminya dengan kata-kata ini.

Selanjutnya, kode berikut memilih fungsi yang akan dipanggil berdasarkan kualifikasi -ulang dari "parameter objek implisit" dari fungsi :

// t.cpp
#include <iostream>

struct test{
  void f() &{ std::cout << "lvalue object\n"; }
  void f() &&{ std::cout << "rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // lvalue
  test().f(); // rvalue
}

Keluaran:

$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object

Semuanya dilakukan untuk memungkinkan Anda mengambil keuntungan dari fakta ketika objek fungsi dipanggil adalah nilai (tidak disebutkan namanya sementara, misalnya). Ambil kode berikut sebagai contoh lebih lanjut:

struct test2{
  std::unique_ptr<int[]> heavy_resource;

  test2()
    : heavy_resource(new int[500]) {}

  operator std::unique_ptr<int[]>() const&{
    // lvalue object, deep copy
    std::unique_ptr<int[]> p(new int[500]);
    for(int i=0; i < 500; ++i)
      p[i] = heavy_resource[i];

    return p;
  }

  operator std::unique_ptr<int[]>() &&{
    // rvalue object
    // we are garbage anyways, just move resource
    return std::move(heavy_resource);
  }
};

Ini mungkin sedikit dibuat-buat, tetapi Anda harus mendapatkan ide.

Perhatikan bahwa Anda dapat menggabungkan kualifikasi-cv ( constdan volatile) dan kualifikasi-ref ( &dan &&).


Catatan: Banyak kutipan standar dan penjelasan resolusi yang berlebihan setelah di sini!

† Untuk memahami bagaimana ini bekerja, dan mengapa jawaban @Nicol Bolas setidaknya sebagian salah, kita harus menggali sedikit dalam standar C ++ (bagian yang menjelaskan mengapa jawaban @ Nicol salah ada di bagian bawah, jika Anda hanya tertarik pada itu).

Fungsi mana yang akan dipanggil ditentukan oleh proses yang disebut resolusi kelebihan beban . Proses ini cukup rumit, jadi kami hanya akan menyentuh bit yang penting bagi kami.

Pertama, penting untuk melihat bagaimana resolusi kelebihan untuk fungsi anggota bekerja:

§13.3.1 [over.match.funcs]

p2 Himpunan fungsi kandidat dapat berisi fungsi anggota dan non-anggota untuk diselesaikan terhadap daftar argumen yang sama. Sehingga daftar argumen dan parameter sebanding dalam set heterogen ini, , fungsi anggota dianggap memiliki parameter tambahan, disebut parameter objek implisit, yang mewakili objek yang fungsi anggotanya disebut . [...]

p3 Demikian pula, bila sesuai, konteksnya dapat membuat daftar argumen yang berisi argumen objek tersirat untuk menunjukkan objek yang akan dioperasikan.

Mengapa kita bahkan perlu membandingkan fungsi anggota dan non-anggota? Operator kelebihan beban, itu sebabnya. Pertimbangkan ini:

struct foo{
  foo& operator<<(void*); // implementation unimportant
};

foo& operator<<(foo&, char const*); // implementation unimportant

Anda tentu ingin yang berikut memanggil fungsi gratis, bukan?

char const* s = "free foo!\n";
foo f;
f << s;

Itu sebabnya fungsi anggota dan non-anggota termasuk dalam yang disebut overload-set. Untuk membuat resolusi kurang rumit, ada bagian tebal dari kutipan standar. Selain itu, ini adalah bagian penting bagi kami (klausa yang sama):

p4 Untuk fungsi anggota non-statis, tipe parameter objek implisit adalah

  • “Referensi nilai cv X ” untuk fungsi yang dideklarasikan tanpa kualifikasi-ulang atau dengan & kualifikasi-ulang

  • “Rvalue referensi ke cv X ” untuk fungsi yang dideklarasikan dengan && ref-kualifikasi

di mana Xkelas yang fungsinya adalah anggota dan cv adalah kualifikasi-cv pada deklarasi fungsi anggota. [...]

p5 Selama resolusi kelebihan [[]] [t] ia parameter objek implisit [...] mempertahankan identitasnya karena konversi pada argumen terkait harus mematuhi aturan tambahan ini:

  • tidak ada objek sementara dapat diperkenalkan untuk memegang argumen untuk parameter objek implisit; dan

  • tidak ada konversi yang ditentukan pengguna dapat diterapkan untuk mencapai jenis yang cocok dengannya

[...]

(Bit terakhir hanya berarti bahwa Anda tidak dapat menipu resolusi kelebihan beban berdasarkan konversi implisit objek fungsi anggota (atau operator) dipanggil.)

Mari kita ambil contoh pertama di bagian atas posting ini. Setelah transformasi yang disebutkan di atas, set overload terlihat seperti ini:

void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'

Kemudian daftar argumen, yang berisi argumen objek tersirat , dicocokkan dengan daftar parameter dari setiap fungsi yang terkandung dalam set-overload. Dalam kasus kami, daftar argumen hanya akan berisi argumen objek itu. Mari kita lihat bagaimana tampilannya:

// first call to 'f' in 'main'
test t;
f1(t); // 't' (lvalue) can match 'test&' (lvalue reference)
       // kept in overload-set
f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference)
       // taken out of overload-set

Jika, setelah semua kelebihan dalam set diuji, hanya satu yang tersisa, resolusi kelebihan berhasil dan fungsi yang terkait dengan kelebihan beban yang ditransformasikan disebut. Hal yang sama berlaku untuk panggilan kedua ke 'f':

// second call to 'f' in 'main'
f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference)
            // taken out of overload-set
f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference)
            // kept in overload-set

Namun perlu dicatat bahwa, jika kami tidak menyediakan ref-kualifikasi apa pun (dan karena itu tidak membebani fungsi), itu f1 akan cocok dengan nilai (masih §13.3.1):

p5 [...] Untuk fungsi anggota non-statis yang dideklarasikan tanpa kualifikasi-ref , aturan tambahan berlaku:

  • bahkan jika parameter objek implisit tidak constdikualifikasi, nilai dapat diikat ke parameter selama semua hal lain argumen dapat dikonversi ke jenis parameter objek implisit.
struct test{
  void f() { std::cout << "lvalue or rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // OK
  test().f(); // OK too
}

Sekarang, mengapa @ Nicol menjawab setidaknya sebagian salah. Dia berkata:

Perhatikan bahwa deklarasi ini mengubah tipe *this.

Itu salah, selalu*this bernilai tinggi :

§5.3.1 [expr.unary.op] p1

*Operator unary melakukan tipuan : ekspresi yang diterapkan harus menjadi pointer ke tipe objek, atau pointer ke tipe fungsi dan hasilnya adalah nilai yang mengacu pada objek atau fungsi yang menjadi titik ekspresi.

§9.3.2 [class.this] p1

Dalam isi fungsi anggota yang tidak statis (9,3), kata kunci thisadalah ekspresi nilai awal yang nilainya adalah alamat objek yang dipanggil untuk fungsi tersebut. Jenis thisdalam fungsi anggota kelas Xadalah X*. [...]

Xeo
sumber
Saya percaya tipe paraneter tepat setelah bagian "setelah transformasi" harus 'foo' bukan 'test'.
ryaner
@ryaner: Selamat mencari, terima kasih. Meskipun bukan parameter tetapi pengidentifikasi kelas fungsi salah. :)
Xeo
oops maaf saya lupa tentang tes kelas mainan bernama ketika saya membaca bagian itu dan berpikir f terkandung dalam foo demikian komentar saya ..
ryaner
Bisakah ini dilakukan dengan konstruktor MyType(int a, double b) &&:?
Germán Diago
2
"Jenis * ini tidak pernah berubah" Anda mungkin harus sedikit lebih jelas bahwa itu tidak berubah berdasarkan kualifikasi r / l-value. tetapi bisa berubah antara const / non-const.
xaxx
78

Ada kasus penggunaan tambahan untuk formulir kualifikasi ulang lvalue. C ++ 98 memiliki bahasa yang memungkinkan constfungsi non- anggota dipanggil untuk instance kelas yang merupakan nilai. Ini mengarah ke semua jenis keanehan yang bertentangan dengan konsep rvalueness dan menyimpang dari cara kerja tipe bawaan:

struct S {
  S& operator ++(); 
  S* operator &(); 
};
S() = S();      // rvalue as a left-hand-side of assignment!
S& foo = ++S(); // oops, dangling reference
&S();           // taking address of rvalue...

Lvalue ref-kualifikasi menyelesaikan masalah ini:

struct S {
  S& operator ++() &;
  S* operator &() &;
  const S& operator =(const S&) &;
};

Sekarang operator bekerja seperti orang-orang dari tipe builtin, hanya menerima nilai.

JohannesD
sumber
28

Katakanlah Anda memiliki dua fungsi di kelas, keduanya dengan nama dan tanda tangan yang sama. Tapi salah satunya dinyatakan const:

void SomeFunc() const;
void SomeFunc();

Jika instance kelas tidak const, resolusi kelebihan akan memilih versi non-const. Jika instansinya adalah const, pengguna hanya dapat memanggil constversi. Dan thispenunjuknya adalah aconst pointer, jadi instance tidak dapat diubah.

Apa yang dilakukan r-value reference untuk ini adalah memungkinkan Anda menambahkan alternatif lain:

void RValueFunc() &&;

Ini memungkinkan Anda untuk memiliki fungsi yang hanya bisa dipanggil jika pengguna memanggilnya melalui nilai-r yang tepat. Jadi jika ini termasuk dalam jenis Object:

Object foo;
foo.RValueFunc(); //error: no `RValueFunc` version exists that takes `this` as l-value.
Object().RValueFunc(); //calls the non-const, && version.

Dengan cara ini, Anda dapat mengkhususkan perilaku berdasarkan pada apakah objek sedang diakses melalui nilai-r atau tidak.

Perhatikan bahwa Anda tidak diizinkan untuk membebani antara versi referensi r-nilai dan versi non-referensi. Artinya, jika Anda memiliki nama fungsi anggota, semua versinya menggunakan kualifikasi nilai l / r this, atau tidak ada yang memilikinya. Anda tidak dapat melakukan ini:

void SomeFunc();
void SomeFunc() &&;

Anda harus melakukan ini:

void SomeFunc() &;
void SomeFunc() &&;

Perhatikan bahwa deklarasi ini mengubah tipe *this. Ini berarti bahwa &&versi semua anggota akses sebagai r-nilai referensi. Jadi menjadi mungkin untuk dengan mudah bergerak dari dalam objek. Contoh yang diberikan dalam versi pertama proposal adalah (catatan: yang berikut ini mungkin tidak benar dengan versi final C ++ 11; langsung dari proposal "r-value from this" awal):

class X {
   std::vector<char> data_;
public:
   // ...
   std::vector<char> const & data() const & { return data_; }
   std::vector<char> && data() && { return data_; }
};

X f();

// ...
X x;
std::vector<char> a = x.data(); // copy
std::vector<char> b = f().data(); // move
Nicol Bolas
sumber
2
Saya pikir Anda perlu std::moveversi kedua, bukan? Juga, mengapa referensi rvalue kembali?
Xeo
1
@Xeo: Karena itulah contohnya dalam proposal; Saya tidak tahu apakah masih berfungsi dengan versi saat ini. Dan alasan pengembalian referensi r-value adalah karena gerakan harus bergantung pada orang yang menangkapnya. Itu belum boleh terjadi, kalau-kalau dia benar-benar ingin menyimpannya di && bukan nilai.
Nicol Bolas
3
Benar, saya agak memikirkan alasan untuk pertanyaan kedua saya. Saya bertanya-tanya, apakah nilai referensi ke anggota sementara memperpanjang masa sementara itu, atau anggota daripadanya? Aku bersumpah aku melihat pertanyaan tentang itu pada SO beberapa waktu yang lalu ...
Xeo
1
@ Xeo: Itu tidak sepenuhnya benar. Resolusi kelebihan akan selalu memilih versi non-const jika ada. Anda harus melakukan pemeran untuk mendapatkan versi const. Saya telah memperbarui pos untuk menjelaskan.
Nicol Bolas
15
Saya pikir saya mungkin menjelaskan, setelah semua saya membuat fitur ini untuk C ++ 11;) Xeo benar dalam bersikeras bahwa itu tidak mengubah jenis *this, namun saya bisa mengerti dari mana kebingungan itu berasal. Ini karena ref-kualifikasi mengubah jenis parameter fungsi implisit (atau "tersembunyi") yang objek "ini" (tanda kutip maksudkan di sini!) Terikat selama resolusi kelebihan dan pemanggilan fungsi. Jadi, tidak ada perubahan *thissejak ini diperbaiki seperti yang dijelaskan Xeo. Alih-alih mengubah "hiddden" parameter untuk menjadikannya sebagai nilai-atau nilai-referensi, sama seperti constfungsi kualifikasi membuatnya constdll.
bronekk