Referensi biasa yang kita kenal — std::string& — hanya mau mengikat objek yang punya nama, dan const std::string& adalah cara lama menerima nilai sementara tanpa menyalinnya. C++11 menambahkan jenis referensi baru: rvalue reference, ditulis T&&. Referensi ini dirancang khusus untuk mengikat rvalue — nilai sementara yang sebentar lagi berakhir — sehingga pemiliknya berhak mengambil alih isinya tanpa rasa bersalah. Di chapter ini kita menguji tiga sifatnya: mengikat temporary, memperpanjang umur temporary sepanjang referensinya hidup, dan menjadi overload yang lebih dipilih ketimbang const T& saat pemanggil mengirim nilai sementara.
Mengikat Temporary dan Memperpanjang Umurnya #
Aturan dasarnya sederhana: T&& mengikat ke rvalue (prvalue maupun xvalue), sedangkan T& mengikat ke lvalue. Yang tidak disangka banyak orang: ketika temporary terikat langsung ke sebuah referensi pada deklarasi variabel, umurnya diperpanjang sampai umur referensi itu berakhir. Mekanisme ini bukan hal baru — const T& sudah melakukannya sejak C++98 — dan T&& mengikuti aturan yang sama. Tanpa pengikatan itu, temporary berakhir di akhir pernyataan tempat ia lahir.
Program berikut memakai class Berita yang mencetak jejak konstruksi dan destruksi, sehingga batas umur setiap objek terbaca dari output. Bagian ketiga juga menunjukkan cara yang aman menyalin isi temporary sebelum ia berakhir:
// rvalue-reference.cpp — T&& mengikat temporary dan memperpanjang umurnya
// Kompilasi: g++ -std=c++23 -Wall -Wextra rvalue-reference.cpp -o rvalue-reference
#include <print>
#include <string>
#include <utility>
class Berita {
public:
explicit Berita(std::string judul) : judul_(std::move(judul)) {
std::println("[konstruksi] {}", judul_);
}
~Berita() { std::println("[destruksi ] {}", judul_); }
const std::string& judul() const { return judul_; }
private:
std::string judul_;
};
// Dua overload: pembacaan saja vs penerimaan yang boleh memindahkan
void periksa(const Berita& b) {
std::println(" diterima sebagai const Berita& (baca saja) -> {}", b.judul());
}
void periksa(Berita&& b) {
std::println(" diterima sebagai Berita&& (boleh dipindahkan) -> {}", b.judul());
}
int main() {
std::println("-- 1) const T& mengikat temporary: umur diperpanjang sampai akhir blok");
{
const Berita& a = Berita("kop-prakata");
std::println(" di akhir blok masih hidup: {}", a.judul());
}
std::println("-- 2) T&& mengikat temporary dengan aturan umur yang sama");
{
Berita&& b = Berita("kop-utama");
std::println(" di akhir blok masih hidup: {}", b.judul());
}
std::println("-- 3) tanpa binding: temporary berakhir di akhir pernyataan");
{
std::string salinan = Berita("kop-sekunder").judul(); // copy selama temporary hidup
std::println(" salinan teks: {}", salinan);
}
std::println("-- 4) overload resolution mengikuti kategori ekspresi");
Berita edisi("edisi-pagi");
periksa(edisi); // lvalue -> const Berita&
periksa(std::move(edisi)); // xvalue -> Berita&&
periksa(Berita("edisi-malam")); // prvalue -> Berita&&
return 0;
}Output yang Dihasilkan:
-- 1) const T& mengikat temporary: umur diperpanjang sampai akhir blok
[konstruksi] kop-prakata
di akhir blok masih hidup: kop-prakata
[destruksi ] kop-prakata
-- 2) T&& mengikat temporary dengan aturan umur yang sama
[konstruksi] kop-utama
di akhir blok masih hidup: kop-utama
[destruksi ] kop-utama
-- 3) tanpa binding: temporary berakhir di akhir pernyataan
[konstruksi] kop-sekunder
[destruksi ] kop-sekunder
salinan teks: kop-sekunder
-- 4) overload resolution mengikuti kategori ekspresi
[konstruksi] edisi-pagi
diterima sebagai const Berita& (baca saja) -> edisi-pagi
diterima sebagai Berita&& (boleh dipindahkan) -> edisi-pagi
[konstruksi] edisi-malam
diterima sebagai Berita&& (boleh dipindahkan) -> edisi-malam
[destruksi ] edisi-malam
[destruksi ] edisi-pagiMari kita bedah:
- Baris 28-32 (binding
const Berita&): temporaryBerita("kop-prakata")dikonstruksi, lalu terikat kea. Destructornya tidak dijalankan di akhir pernyataan, melainkan di akhir blok — terbukti dari urutan cetak: “masih hidup” muncul sebelum label destruksi. - Baris 34-38 (binding
Berita&&): perilaku identik dengan bagian pertama.badalah rvalue reference ke temporary yang umurnya diperpanjang sampai akhir blok — inilah kandidat pemindahan yang masih bisa kita pakai selama beberapa baris. - Baris 40-44 (tanpa binding):
Berita("kop-sekunder").judul()mengembalikan referensi ke anggota temporary; kita menyalin teksnya kesalinanselama ekspresi masih berjalan, lalu temporary berakhir tepat di akhir pernyataan — label destruksi tercetak sebelum baris “salinan teks”. - Baris 46-50 (overload resolution):
edisiadalah lvalue sehingga versiconst Berita&yang dipilih;std::move(edisi)menghasilkan xvalue sehingga versiBerita&&menang; temporaryBerita("edisi-malam")(prvalue) juga jatuh ke versiBerita&&. Di sini kita belum memindahkan apa pun — versi&&hanya membaca — tetapi haknya sudah terbuka.
const T& versus T&&: Siapa Menang untuk Rvalue #
Sebelum C++11, const T& adalah penerima serba bisa: ia mengikat lvalue dan rvalue sekaligus, karena menjanjikan tidak akan mengubah apa pun. Masalahnya, janji itu membuat kita kehilangan informasi — compiler tidak tahu mana pemanggil yang mengirim objek sementara yang boleh diambil alih. Dengan hadirnya T&&, aturan pemilihannya menjadi tegas:
| Argumen pemanggil | f(const T&) | f(T&&) | Terpilih |
|---|---|---|---|
lvalue obj | cocok | tidak bisa terikat | const T& |
| rvalue (prvalue/xvalue) | cocok | cocok | T&& (lebih spesifik) |
Ketika keduanya sama-sama bisa menerima, T&& menang karena lebih spesifik. Prinsip ini yang membuat move constructor dan overload penerima rvalue bekerja: container standar seperti std::vector menyediakan pasangan overload push_back(const T&) dan push_back(T&&) dengan pola yang persis sama.
Sampai di sini T&& baru kita pakai sebagai “penerima rvalue yang sopan”. Kekuatan sesungguhnya muncul ketika referensi itu dipakai untuk memindahkan isi objek: itulah tugas move constructor yang kita bedah di chapter berikutnya.
Rujukan resmi: cppreference — reference declaration .