Deklarator referensi rvalue: &&

Menyimpan referensi ke ekspresi rvalue.

Sintaks

rvalue-reference-type-id:
type-specifier-seq&&attribute-specifier-seqoptptr-abstract-declaratoropt

Keterangan

Referensi Rvalue memungkinkan Anda membedakan lvalue dari rvalue. Referensi Lvalue dan referensi rvalue secara sintetis dan semantik mirip, tetapi mengikuti aturan yang sedikit berbeda. Untuk informasi selengkapnya tentang lvalue dan rvalue, lihat Lvalues dan Rvalues. Untuk informasi selengkapnya tentang referensi lvalue, lihat Deklarator Referensi Lvalue: &.

Bagian berikut menjelaskan bagaimana referensi rvalue mendukung implementasi semantik pemindahan dan penerusan yang sempurna.

Memindahkan semantik

Referensi Rvalue mendukung implementasi semantik pemindahan, yang dapat secara signifikan meningkatkan performa aplikasi Anda. Memindahkan semantik memungkinkan Anda menulis kode yang mentransfer sumber daya (seperti memori yang dialokasikan secara dinamis) dari satu objek ke objek lainnya. Memindahkan semantik berfungsi karena memungkinkan transfer sumber daya dari objek sementara: yang tidak dapat dirujuk di tempat lain dalam program.

Untuk menerapkan semantik pemindahan, Anda biasanya menyediakan konstruktor pemindahan, dan secara opsional operator penetapan pemindahan (operator=), ke kelas Anda. Operasi salin dan penugasan yang sumbernya rvalues kemudian secara otomatis memanfaatkan semantik pemindahan. Tidak seperti konstruktor salin default, pengkompilasi tidak menyediakan konstruktor pemindahan default. Untuk informasi selengkapnya tentang cara menulis dan menggunakan konstruktor pemindahan, lihat Memindahkan konstruktor dan memindahkan operator penugasan.

Anda juga dapat membebani fungsi dan operator biasa untuk memanfaatkan semantik pemindahan. Visual Studio 2010 memperkenalkan pemindahan semantik ke Pustaka Standar C++. Misalnya, string kelas menerapkan operasi yang menggunakan semantik pemindahan. Pertimbangkan contoh berikut yang menggabungkan beberapa string dan mencetak hasilnya:

// string_concatenation.cpp
// compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;

int main()
{
   string s = string("h") + "e" + "ll" + "o";
   cout << s << endl;
}

Sebelum Visual Studio 2010, setiap panggilan untuk operator+ mengalokasikan dan mengembalikan objek sementara string baru (rvalue). operator+ tidak dapat menambahkan satu string ke string lainnya karena tidak tahu apakah string sumber adalah lvalues atau rvalues. Jika string sumber keduanya lvalue, string tersebut mungkin direferensikan di tempat lain dalam program, sehingga tidak boleh dimodifikasi. Anda dapat memodifikasi operator+ untuk mengambil rvalue dengan menggunakan referensi rvalue, yang tidak dapat direferensikan di tempat lain dalam program. Dengan perubahan ini, operator+ sekarang dapat menambahkan satu string ke string lainnya. Perubahan secara signifikan mengurangi jumlah alokasi memori dinamis yang string harus dilakukan kelas. Untuk informasi selengkapnya tentang string kelas, lihat basic_string Kelas.

Pemindahan semantik juga membantu ketika pengkompilasi tidak dapat menggunakan Return Value Optimization (RVO) atau Named Return Value Optimization (NRVO). Dalam kasus ini, pengkompilasi memanggil konstruktor pemindahan jika jenis mendefinisikannya.

Untuk lebih memahami pemindahan semantik, pertimbangkan contoh memasukkan elemen ke dalam vector objek. Jika kapasitas vector objek terlampaui, vector objek harus merealokasi memori yang cukup untuk elemennya, lalu menyalin setiap elemen ke lokasi memori lain untuk memberi ruang bagi elemen yang dimasukkan. Ketika operasi penyisipan menyalin elemen, pertama-tama membuat elemen baru. Kemudian memanggil konstruktor salin untuk menyalin data dari elemen sebelumnya ke elemen baru. Akhirnya, ia menghancurkan elemen sebelumnya. Memindahkan semantik memungkinkan Anda memindahkan objek secara langsung tanpa harus membuat alokasi memori dan operasi salin yang mahal.

Untuk memanfaatkan pemindahan semantik dalam vector contoh, Anda dapat menulis konstruktor pemindahan untuk memindahkan data dari satu objek ke objek lainnya.

Untuk informasi selengkapnya tentang pengenalan pemindahan semantik ke Pustaka Standar C++ di Visual Studio 2010, lihat Pustaka Standar C++.

Penerusan sempurna

Penerusan sempurna mengurangi kebutuhan akan fungsi yang kelebihan beban dan membantu menghindari masalah penerusan. Masalah penerusan dapat terjadi ketika Anda menulis fungsi generik yang mengambil referensi sebagai parameternya. Jika meneruskan (atau meneruskan) parameter ini ke fungsi lain, misalnya, jika mengambil parameter jenis const T&, maka fungsi yang disebut tidak dapat mengubah nilai parameter tersebut. Jika fungsi generik mengambil parameter jenis T&, maka fungsi tidak dapat dipanggil dengan menggunakan rvalue (seperti objek sementara atau literal bilangan bulat).

Biasanya, untuk menyelesaikan masalah ini, Anda harus menyediakan versi fungsi generik yang berlebihan yang mengambil keduanya T& dan const T& untuk setiap parameternya. Akibatnya, jumlah fungsi yang kelebihan beban meningkat secara eksponensial dengan jumlah parameter. Referensi Rvalue memungkinkan Anda menulis satu versi fungsi yang menerima argumen arbitrer. Kemudian fungsi itu dapat meneruskannya ke fungsi lain seolah-olah fungsi lain telah dipanggil secara langsung.

Pertimbangkan contoh berikut yang mendeklarasikan empat jenis, , W, XY, dan Z. Konstruktor untuk setiap jenis mengambil kombinasi yang berbeda dari const dan referensi non-lvalueconst sebagai parameternya.

struct W
{
   W(int&, int&) {}
};

struct X
{
   X(const int&, int&) {}
};

struct Y
{
   Y(int&, const int&) {}
};

struct Z
{
   Z(const int&, const int&) {}
};

Misalkan Anda ingin menulis fungsi generik yang menghasilkan objek. Contoh berikut menunjukkan salah satu cara untuk menulis fungsi ini:

template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2)
{
   return new T(a1, a2);
}

Contoh berikut menunjukkan panggilan yang valid ke factory fungsi:

int a = 4, b = 5;
W* pw = factory<W>(a, b);

Namun, contoh berikut tidak berisi panggilan yang valid ke factory fungsi . Ini karena factory mengambil referensi lvalue yang dapat dimodifikasi sebagai parameternya, tetapi dipanggil dengan menggunakan rvalue:

Z* pz = factory<Z>(2, 2);

Biasanya, untuk menyelesaikan masalah ini, Anda harus membuat versi factory fungsi yang kelebihan beban untuk setiap kombinasi A& parameter dan const A& . Referensi Rvalue memungkinkan Anda menulis satu versi fungsi, seperti yang factory ditunjukkan dalam contoh berikut:

template <typename T, typename A1, typename A2>
T* factory(A1&& a1, A2&& a2)
{
   return new T(std::forward<A1>(a1), std::forward<A2>(a2));
}

Contoh ini menggunakan referensi rvalue sebagai parameter ke factory fungsi. Tujuan fungsi std::forward ini adalah untuk meneruskan parameter fungsi pabrik ke konstruktor kelas templat.

Contoh berikut menunjukkan main fungsi yang menggunakan fungsi yang direvisi factory untuk membuat instans Wkelas , , XY, dan Z . Fungsi yang direvisi meneruskan factory parameternya (baik lvalue atau rvalue) ke konstruktor kelas yang sesuai.

int main()
{
   int a = 4, b = 5;
   W* pw = factory<W>(a, b);
   X* px = factory<X>(2, b);
   Y* py = factory<Y>(a, 2);
   Z* pz = factory<Z>(2, 2);

   delete pw;
   delete px;
   delete py;
   delete pz;
}

Properti referensi rvalue

Anda dapat membebani fungsi untuk mengambil referensi lvalue dan referensi rvalue.

Dengan membebani fungsi untuk mengambil const referensi lvalue atau referensi rvalue, Anda dapat menulis kode yang membedakan antara objek yang tidak dapat dimodifikasi (lvalues) dan nilai sementara yang dapat dimodifikasi (rvalue). Anda dapat meneruskan objek ke fungsi yang mengambil referensi rvalue kecuali objek ditandai sebagai const. Contoh berikut menunjukkan fungsi f, yang kelebihan beban untuk mengambil referensi lvalue dan referensi rvalue. Fungsi memanggil mainf dengan lvalue dan rvalue.

// reference-overload.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void f(const MemoryBlock&)
{
   cout << "In f(const MemoryBlock&). This version can't modify the parameter." << endl;
}

void f(MemoryBlock&&)
{
   cout << "In f(MemoryBlock&&). This version can modify the parameter." << endl;
}

int main()
{
   MemoryBlock block;
   f(block);
   f(MemoryBlock());
}

Contoh ini menghasilkan output berikut:

In f(const MemoryBlock&). This version can't modify the parameter.
In f(MemoryBlock&&). This version can modify the parameter.

Dalam contoh ini, panggilan pertama untuk f meneruskan variabel lokal (lvalue) sebagai argumennya. Panggilan kedua untuk f meneruskan objek sementara sebagai argumennya. Karena objek sementara tidak dapat direferensikan di tempat lain dalam program, panggilan mengikat ke versi f kelebihan beban yang mengambil referensi rvalue, yang bebas untuk memodifikasi objek.

Pengkompilasi memperlakukan referensi rvalue bernama sebagai lvalue dan referensi rvalue yang tidak disebutkan namanya sebagai rvalue.

Fungsi yang mengambil referensi rvalue sebagai parameter memperlakukan parameter sebagai lvalue dalam isi fungsi. Pengkompilasi memperlakukan referensi rvalue bernama sebagai lvalue. Ini karena objek bernama dapat dirujuk oleh beberapa bagian program. Sangat berbahaya untuk memungkinkan beberapa bagian program memodifikasi atau menghapus sumber daya dari objek tersebut. Misalnya, jika beberapa bagian program mencoba mentransfer sumber daya dari objek yang sama, hanya transfer pertama yang berhasil.

Contoh berikut menunjukkan fungsi g, yang kelebihan beban untuk mengambil referensi lvalue dan referensi rvalue. Fungsi f mengambil referensi rvalue sebagai parameternya (referensi rvalue bernama) dan mengembalikan referensi rvalue (referensi rvalue yang tidak disebutkan namanya). Dalam panggilan ke g dari f, resolusi kelebihan beban memilih versi g yang mengambil referensi lvalue karena isi f memperlakukan parameternya sebagai lvalue. Dalam panggilan ke g dari main, resolusi kelebihan beban memilih versi g yang mengambil referensi rvalue karena f mengembalikan referensi rvalue.

// named-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void g(const MemoryBlock&)
{
   cout << "In g(const MemoryBlock&)." << endl;
}

void g(MemoryBlock&&)
{
   cout << "In g(MemoryBlock&&)." << endl;
}

MemoryBlock&& f(MemoryBlock&& block)
{
   g(block);
   return move(block);
}

int main()
{
   g(f(MemoryBlock()));
}

Contoh ini menghasilkan output berikut:

In g(const MemoryBlock&).
In g(MemoryBlock&&).

Dalam contoh, main fungsi meneruskan rvalue ke f. Tubuh memperlakukan f parameter bernama sebagai lvalue. Panggilan dari f untuk g mengikat parameter ke referensi lvalue (versi gkelebihan beban pertama ).

  • Anda dapat melemparkan lvalue ke referensi rvalue.

Fungsi Pustaka std::move Standar C++ memungkinkan Anda mengonversi objek ke referensi rvalue ke objek tersebut. Anda juga dapat menggunakan static_cast kata kunci untuk melemparkan lvalue ke referensi rvalue, seperti yang ditunjukkan dalam contoh berikut:

// cast-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void g(const MemoryBlock&)
{
   cout << "In g(const MemoryBlock&)." << endl;
}

void g(MemoryBlock&&)
{
   cout << "In g(MemoryBlock&&)." << endl;
}

int main()
{
   MemoryBlock block;
   g(block);
   g(static_cast<MemoryBlock&&>(block));
}

Contoh ini menghasilkan output berikut:

In g(const MemoryBlock&).
In g(MemoryBlock&&).

Templat fungsi menyimpulkan jenis argumen templatnya lalu menggunakan aturan ciutkan referensi.

Templat fungsi yang meneruskan (atau meneruskan) parameternya ke fungsi lain adalah pola umum. Penting untuk memahami cara kerja pengurangan jenis templat untuk templat fungsi yang mengambil referensi rvalue.

Jika argumen fungsi adalah rvalue, pengkompilasi menyimpulkan argumen menjadi referensi rvalue. Misalnya, asumsikan Anda meneruskan referensi rvalue ke objek jenis X ke templat fungsi yang mengambil jenis T&& sebagai parameternya. Pengurangan argumen templat menyimpulkan T menjadi X, sehingga parameter memiliki jenis X&&. Jika argumen fungsi adalah lvalue atau const lvalue, pengkompilasi menyimpulkan jenisnya menjadi referensi lvalue atau const referensi lvalue dari jenis tersebut.

Contoh berikut mendeklarasikan satu templat struktur lalu mengkhususkannya untuk berbagai jenis referensi. Fungsi ini print_type_and_value mengambil referensi rvalue sebagai parameternya dan meneruskannya ke versi khusus metode yang S::print sesuai. Fungsi ini main menunjukkan berbagai cara untuk memanggil S::print metode .

// template-type-deduction.cpp
// Compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;

template<typename T> struct S;

// The following structures specialize S by
// lvalue reference (T&), const lvalue reference (const T&),
// rvalue reference (T&&), and const rvalue reference (const T&&).
// Each structure provides a print method that prints the type of
// the structure and its parameter.

template<typename T> struct S<T&> {
   static void print(T& t)
   {
      cout << "print<T&>: " << t << endl;
   }
};

template<typename T> struct S<const T&> {
   static void print(const T& t)
   {
      cout << "print<const T&>: " << t << endl;
   }
};

template<typename T> struct S<T&&> {
   static void print(T&& t)
   {
      cout << "print<T&&>: " << t << endl;
   }
};

template<typename T> struct S<const T&&> {
   static void print(const T&& t)
   {
      cout << "print<const T&&>: " << t << endl;
   }
};

// This function forwards its parameter to a specialized
// version of the S type.
template <typename T> void print_type_and_value(T&& t)
{
   S<T&&>::print(std::forward<T>(t));
}

// This function returns the constant string "fourth".
const string fourth() { return string("fourth"); }

int main()
{
   // The following call resolves to:
   // print_type_and_value<string&>(string& && t)
   // Which collapses to:
   // print_type_and_value<string&>(string& t)
   string s1("first");
   print_type_and_value(s1);

   // The following call resolves to:
   // print_type_and_value<const string&>(const string& && t)
   // Which collapses to:
   // print_type_and_value<const string&>(const string& t)
   const string s2("second");
   print_type_and_value(s2);

   // The following call resolves to:
   // print_type_and_value<string&&>(string&& t)
   print_type_and_value(string("third"));

   // The following call resolves to:
   // print_type_and_value<const string&&>(const string&& t)
   print_type_and_value(fourth());
}

Contoh ini menghasilkan output berikut:

print<T&>: first
print<const T&>: second
print<T&&>: third
print<const T&&>: fourth

Untuk mengatasi setiap panggilan ke print_type_and_value fungsi, pengkompilasi terlebih dahulu melakukan pengurangan argumen templat. Pengkompilasi kemudian menerapkan aturan ciutkan referensi saat mengganti jenis parameter dengan argumen templat yang disimpulkan. Misalnya, meneruskan variabel s1 lokal ke print_type_and_value fungsi menyebabkan pengkompilasi menghasilkan tanda tangan fungsi berikut:

print_type_and_value<string&>(string& && t)

Pengkompilasi menggunakan aturan ciutkan referensi untuk mengurangi tanda tangan:

print_type_and_value<string&>(string& t)

Versi print_type_and_value fungsi ini kemudian meneruskan parameternya ke versi khusus metode yang S::print benar.

Tabel berikut ini meringkas aturan ciutkan referensi untuk pengurangan jenis argumen templat:

Jenis yang diperluas Tipe yang diciutkan
T& & T&
T& && T&
T&& & T&
T&& && T&&

Pengurangan argumen templat adalah elemen penting untuk menerapkan penerusan yang sempurna. Bagian Penerusan sempurna menjelaskan penerusan yang sempurna secara lebih rinci.

Ringkasan

Referensi rvalue membedakan lvalues dari rvalues. Untuk meningkatkan performa aplikasi Anda, aplikasi tersebut dapat menghilangkan kebutuhan akan alokasi memori dan operasi penyalinan yang tidak perlu. Mereka juga memungkinkan Anda menulis fungsi yang menerima argumen arbitrer. Fungsi itu dapat meneruskannya ke fungsi lain seolah-olah fungsi lain telah dipanggil secara langsung.

Baca juga

Ekspresi dengan operator unary
Deklarator referensi Lvalue: &
Lvalues dan rvalues
Memindahkan konstruktor dan memindahkan operator penetapan (C++)
Pustaka Standar C++