Apakah referensi rvalue ke const ada gunanya?

Jawaban:

78

Mereka terkadang berguna. Draf C ++ 0x sendiri menggunakannya di beberapa tempat, misalnya:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Dua kelebihan beban di atas memastikan bahwa fungsi ref(T&)dan lainnya cref(const T&)tidak terikat ke nilai r (yang jika tidak dimungkinkan).

Memperbarui

Saya baru saja memeriksa standar resmi N3290 , yang sayangnya tidak tersedia untuk umum, dan ada di 20.8 Objek fungsi [function.objects] / p2:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Kemudian saya memeriksa draf post-C ++ 11 terbaru, yang tersedia untuk umum, N3485 , dan di 20,8 Objek fungsi [function.objects] / p2 masih mengatakan:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
Howard Hinnant
sumber
Melihat cppreference tampaknya ini bukan masalahnya lagi. Ada ide mengapa? Ada tempat lain const T&&yang digunakan?
Pubby
60
Mengapa Anda memasukkan kode yang sama dalam jawaban Anda tiga kali? Saya mencoba mencari perbedaan terlalu lama.
typ1232
9
@ typ1232: Tampaknya saya memperbarui jawaban hampir 2 tahun setelah saya menjawabnya, karena kekhawatiran di komentar bahwa fungsi yang dirujuk tidak lagi muncul. Saya melakukan salin / tempel dari N3290, dan dari draf N3485 terbaru untuk menunjukkan bahwa fungsinya masih muncul. Menggunakan salin / tempel, dalam pikiran saya pada saat itu, adalah cara terbaik untuk memastikan bahwa lebih banyak mata daripada milik saya dapat mengonfirmasi bahwa saya tidak mengabaikan beberapa perubahan kecil pada tanda tangan ini.
Howard Hinnant
1
@ kevinarpe: The "other overloads" (tidak ditampilkan di sini, tetapi dalam standar) mengambil referensi lvalue, dan tidak dihapus. Kelebihan beban yang ditampilkan di sini lebih cocok untuk nilai r daripada kelebihan beban yang mengambil referensi nilai l. Jadi argumen rvalue mengikat kelebihan beban yang ditampilkan di sini, dan kemudian menyebabkan kesalahan waktu kompilasi karena kelebihan beban ini dihapus.
Howard Hinnant
1
Penggunaan const T&&mencegah seseorang dengan bodohnya menggunakan template eksplisit args dari formulir ref<const A&>(...). Itu bukan argumen yang sangat kuat, tetapi biaya const T&&lebih T&&cukup minim.
Howard Hinnant
6

Semantik mendapatkan referensi nilai konstanta (dan bukan untuk =delete) adalah untuk mengatakan:

  • kami tidak mendukung operasi untuk lvalues!
  • meskipun, kami masih menyalin , karena kami tidak dapat memindahkan sumber daya yang diteruskan, atau karena tidak ada arti sebenarnya untuk "memindahkan" itu.

Kasus penggunaan berikut bisa jadi IMHO kasus penggunaan yang baik untuk referensi nilai r ke const , meskipun bahasa memutuskan untuk tidak menggunakan pendekatan ini (lihat posting SO asli ).


Kasus: konstruktor pointer pintar dari pointer mentah

Biasanya disarankan untuk menggunakan make_uniquedan make_shared, tetapi keduanya unique_ptrdan shared_ptrdapat dibuat dari penunjuk mentah. Kedua konstruktor mendapatkan pointer berdasarkan nilai dan menyalinnya. Keduanya memungkinkan (yaitu dalam arti: tidak mencegah ) penggunaan lanjutan dari penunjuk asli yang diteruskan kepada mereka di konstruktor.

Kode berikut mengkompilasi dan hasil dengan gratis ganda :

int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;

Keduanya unique_ptrdan shared_ptrdapat mencegah hal di atas jika konstruktor relevan mereka mengharapkan untuk mendapatkan pointer mentah sebagai nilai konstanta , misalnya untuk unique_ptr:

unique_ptr(T* const&& p) : ptr{p} {}

Dalam hal ini kode bebas ganda di atas tidak akan dikompilasi, tetapi yang berikut akan:

std::unique_ptr<int> p1 { std::move(ptr) }; // more verbose: user moves ownership
std::unique_ptr<int> p2 { new int(7) };     // ok, rvalue

Catatan itu ptrmasih bisa digunakan setelah dipindahkan, jadi potensi bug tidak hilang sama sekali. Tetapi jika pengguna diminta untuk memanggil std::movebug seperti itu akan jatuh ke dalam aturan umum: jangan gunakan sumber daya yang dipindahkan.


Seseorang dapat bertanya: OK, tapi mengapa T* const&& p ?

Alasannya sederhana, untuk memungkinkan pembuatan dari unique_ptr pointer const . Ingatlah bahwa referensi nilai r lebih umum daripada hanya referensi nilai r karena ia menerima keduanya constdan non-const. Jadi kami dapat mengizinkan yang berikut:

int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };

ini tidak akan berjalan jika kita hanya mengharapkan rvalue reference (kesalahan kompilasi: tidak dapat mengikat const rvalue ke rvalue ).


Bagaimanapun, ini sudah terlambat untuk mengusulkan hal seperti itu. Tapi ide ini memang menyajikan penggunaan yang wajar dari rujukan nilai r ke const .

Amir Kirsh
sumber
Saya termasuk di antara orang-orang dengan ide-ide serupa, tetapi saya tidak memiliki dukungan untuk maju.
Red. Gelombang
"auto p = std :: unique_ptr {std :: move (ptr)};" tidak dapat dikompilasi dengan kesalahan "pengurangan argumen template kelas gagal". Saya pikir itu harus "unique_ptr <int>".
Zehui Lin
4

Mereka diperbolehkan dan bahkan fungsi diberi peringkat berdasarkan const, tetapi karena Anda tidak dapat berpindah dari objek const yang dirujuk const Foo&&, mereka tidak berguna.

Gene Bushuyev
sumber
Apa sebenarnya yang Anda maksud dengan ucapan "peringkat"? Ada hubungannya dengan resolusi overload, kurasa?
fredoverflow
Mengapa Anda tidak bisa pindah dari const rvalue-ref, jika tipe yang diberikan memiliki ctor yang bergerak yang mengambil const rvalue-ref?
Fred Nurk
6
@FredOverflow, peringkat kelebihan beban adalah ini:const T&, T&, const T&&, T&&
Gene Bushuyev
2
@Fred: Bagaimana Anda memindahkan tanpa mengubah sumber?
fredoverflow
3
@Fred: Anggota data yang dapat berubah, atau mungkin pindah untuk tipe hipotetis ini tidak memerlukan modifikasi anggota data.
Fred Nurk
2

Selain std :: ref , pustaka standar juga menggunakan referensi rvalue const di std :: as_const untuk tujuan yang sama.

template <class T>
void as_const(const T&&) = delete;

Ini juga digunakan sebagai nilai kembalian di std :: opsional saat mendapatkan nilai yang dibungkus:

constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;

Serta di std :: get :

template <class T, class... Types>
constexpr const T&& get(const std::variant<Types...>&& v);
template< class T, class... Types >
constexpr const T&& get(const tuple<Types...>&& t) noexcept;

Ini mungkin untuk menjaga kategori nilai serta keteguhan pembungkus saat mengakses nilai yang dibungkus.

Ini membuat perbedaan apakah fungsi yang memenuhi syarat nilai rvalue const dapat dipanggil pada objek yang dibungkus. Yang mengatakan, saya tidak tahu kegunaan apa pun untuk fungsi yang memenuhi syarat nilai rvalue const.

eerorika
sumber
1

Saya tidak dapat memikirkan situasi di mana ini akan berguna secara langsung, tetapi ini mungkin digunakan secara tidak langsung:

template<class T>
void f(T const &x) {
  cout << "lvalue";
}
template<class T>
void f(T &&x) {
  cout << "rvalue";
}

template<class T>
void g(T &x) {
  f(T());
}

template<class T>
void h(T const &x) {
  g(x);
}

T dalam g adalah T konst, jadi f 's x adalah T konst &&.

Kemungkinan ini menghasilkan kesalahan comile di f (ketika mencoba untuk memindahkan atau menggunakan objek), tetapi f bisa mengambil rvalue-ref sehingga tidak bisa dipanggil pada lvalues, tanpa memodifikasi rvalue (seperti pada terlalu sederhana contoh di atas).

Fred Nurk
sumber