Объявление ссылки
Объявляет именованную переменную как ссылку, то есть как псевдоним уже существующего объекта или функции.
Синтаксис
Объявлением переменной-ссылки является любое простое объявление, чей декларатор имеет вид
& attr (необязательно) declarator | (1) | |
&& attr (необязательно) declarator | (2) | (начиная с C++11) |
S& D; объявляет D как ссылку на lvalue типа, определяемого decl-specifier-seq S.S&& D; объявляет D как ссылку на rvalue типа, определяемого decl-specifier-seq S.| declarator | - | любой декларатор, кроме другого декларатора ссылки (ссылок на ссылки не существует) |
| attr | - | (начиная с C++11) список атрибутов |
Ссылка должна быть инициализирована так, чтобы ссылаться на допустимый объект или функцию: см. инициализацию ссылки.
Тип «ссылка на (возможно, cv-квалифицированный) void» сформировать нельзя.
Типы ссылок не могут быть cv-квалифицированы на верхнем уровне; в объявлении для этого нет синтаксиса, а если квалификатор добавлен к имени typedef или спецификатору decltype,(начиная с C++11) или параметру шаблона типа, он игнорируется.
Ссылки не являются объектами; они не обязательно занимают память, хотя компилятор может выделить для них память, если это необходимо для реализации требуемой семантики (например, нестатический член-данные ссылочного типа обычно увеличивает размер класса на величину, необходимую для хранения адреса в памяти).
Поскольку ссылки не являются объектами, массивов ссылок, указателей на ссылки и ссылок на ссылки не существует:
int& a[3]; // error int&* p; // error int& &r; // error
Схлопывание ссылокСсылки на ссылки можно сформировать с помощью манипуляций с типами в шаблонах или typedef-объявлениях; в этом случае применяются правила схлопывания ссылок: ссылка на rvalue на ссылку на rvalue схлопывается в ссылку на rvalue, а все остальные комбинации образуют ссылку на lvalue: typedef int& lref; typedef int&& rref; int n; lref& r1 = n; // type of r1 is int& lref&& r2 = n; // type of r2 is int& rref& r3 = n; // type of r3 is int& rref&& r4 = 1; // type of r4 is int&& (Это правило вместе со специальными правилами выведения аргументов шаблона в случаях, когда в шаблоне функции используется | (начиная с C++11) |
Ссылки на lvalue
Ссылки на lvalue можно использовать для создания псевдонима существующего объекта (при необходимости с другой cv-квалификацией):
#include <iostream>
#include <string>
int main()
{
std::string s = "Ex";
std::string& r1 = s;
const std::string& r2 = s;
r1 += "ample"; // modifies s
// r2 += "!"; // error: cannot modify through reference to const
std::cout << r2 << '\n'; // prints s, which now holds "Example"
}
Их также можно использовать для реализации семантики передачи по ссылке при вызове функций:
#include <iostream>
#include <string>
void double_string(std::string& s)
{
s += s; // 's' is the same object as main()'s 'str'
}
int main()
{
std::string str = "Test";
double_string(str);
std::cout << str << '\n';
}
Если возвращаемый тип функции — ссылка на lvalue, выражение вызова функции становится выражением lvalue:
#include <iostream>
#include <string>
char& char_number(std::string& s, std::size_t n)
{
return s.at(n); // string::at() returns a reference to char
}
int main()
{
std::string str = "Test";
char_number(str, 1) = 'a'; // the function call is lvalue, can be assigned to
std::cout << str << '\n';
}
Ссылки на rvalueСсылки на rvalue можно использовать, чтобы продлить время жизни временных объектов (обратите внимание: ссылки на const lvalue тоже могут продлить время жизни временных объектов, но через них нельзя изменять эти объекты): #include <iostream>
#include <string>
int main()
{
std::string s1 = "Test";
// std::string&& r1 = s1; // error: can't bind to lvalue
const std::string& r2 = s1 + s1; // okay: lvalue reference to const extends lifetime
// r2 += "Test"; // error: can't modify through reference to const
std::string&& r3 = s1 + s1; // okay: rvalue reference extends lifetime
r3 += "Test"; // okay: can modify through reference to non-const
std::cout << r3 << '\n';
}
Что важнее, если у функции есть перегрузки со ссылкой на rvalue и ссылкой на lvalue, перегрузка со ссылкой на rvalue связывается с rvalue (включая prvalue и xvalue), а перегрузка со ссылкой на lvalue — с lvalue: #include <iostream>
#include <utility>
void f(int& x)
{
std::cout << "lvalue reference overload f(" << x << ")\n";
}
void f(const int& x)
{
std::cout << "lvalue reference to const overload f(" << x << ")\n";
}
void f(int&& x)
{
std::cout << "rvalue reference overload f(" << x << ")\n";
}
int main()
{
int i = 1;
const int ci = 2;
f(i); // calls f(int&)
f(ci); // calls f(const int&)
f(3); // calls f(int&&)
// would call f(const int&) if f(int&&) overload wasn't provided
f(std::move(i)); // calls f(int&&)
// rvalue reference variables are lvalues when used in expressions
int&& x = 1;
f(x); // calls f(int& x)
f(std::move(x)); // calls f(int&& x)
}
Это позволяет автоматически выбирать подходящие конструкторы перемещения, операторы присваивания перемещением и другие функции с поддержкой перемещения (например, Поскольку ссылки на rvalue могут связываться с xvalue, они могут ссылаться на нет временные объекты: int i2 = 42; int&& rri = std::move(i2); // binds directly to i2 Это позволяет переместить данные из объекта в области видимости, который больше не нужен: std::vector<int> v{1, 2, 3, 4, 5};
std::vector<int> v2(std::move(v)); // binds an rvalue reference to v
assert(v.empty());
Пересылающие ссылкиПересылающие ссылки — это особый вид ссылок, сохраняющих категорию значения аргумента функции, что позволяет пересылать его с помощью template<class T>
int f(T&& x) // x is a forwarding reference
{
return g(std::forward<T>(x)); // and so can be forwarded
}
int main()
{
int i;
f(i); // argument is lvalue, calls f<int&>(int&), std::forward<int&>(x) is lvalue
f(0); // argument is rvalue, calls f<int>(int&&), std::forward<int>(x) is rvalue
}
template<class T>
int g(const T&& x); // x is not a forwarding reference: const T is not cv-unqualified
template<class T>
struct A
{
template<class U>
A(T&& x, U&& y, int* p); // x is not a forwarding reference: T is not a
// type template parameter of the constructor,
// but y is a forwarding reference
};
2) auto&&, кроме случаев вывода из списка инициализации в фигурных скобках или представления параметра шаблона шаблона класса при выведении аргументов шаблона класса(начиная с C++17): auto&& vec = foo(); // foo() may be lvalue or rvalue, vec is a forwarding reference
auto i = std::begin(vec); // works either way
(*i)++; // works either way
g(std::forward<decltype(vec)>(vec)); // forwards, preserving value category
for (auto&& x: f())
{
// x is a forwarding reference; this is a common way to use range for in generic code
}
auto&& z = {1, 2, 3}; // *not* a forwarding reference (special case for initializer lists)
См. также выведение аргументов шаблона и | (начиная с C++11) |
Висячие ссылки
Хотя при инициализации ссылки всегда указывают на допустимые объекты или функции, можно создать программу, в которой время жизни объекта, на который ссылаются, заканчивается, а ссылка остается доступной (висячая ссылка).
Пусть выражение expr имеет ссылочный тип, а target — это объект или функция, обозначаемые ссылкой:
- Если указатель на
targetбыл бы допустим в контексте вычисленияexpr, результат обозначаетtarget. - В противном случае поведение не определено.
std::string& f()
{
std::string s = "Example";
return s; // exits the scope of s:
// its destructor is called and its storage deallocated
}
std::string& r = f(); // dangling reference
std::cout << r; // undefined behavior: reads from a dangling reference
std::string s = f(); // undefined behavior: copy-initializes from a dangling reference
Обратите внимание: ссылки на rvalue и ссылки на const lvalue продлевают время жизни временных объектов (правила и исключения см. в разделе Инициализация ссылки).
Если объект, на который ссылаются, был уничтожен (например, явным вызовом деструктора), но память не была освобождена, ссылку на объект с истекшим временем жизни можно использовать ограниченным образом; она может снова стать допустимой, если объект будет создан заново в той же памяти (подробности см. в разделе Доступ за пределами времени жизни).
Ссылки с недоступным типом
Попытка связать ссылку с объектом, если преобразованный инициализатор является lvalue(до C++11)glvalue(начиная с C++11), через который тип объекта недоступен, приводит к неопределенному поведению:
char x alignas(int);
int& ir = *reinterpret_cast<int*>(&x); // undefined behavior:
// initializer refers to char object
Ссылки, несовместимые при вызове
Попытка связать ссылку с функцией, если преобразованный инициализатор является lvalue(до C++11)glvalue(начиная с C++11), тип которого не совместим при вызове с типом определения функции, приводит к неопределенному поведению:
void f(int);
using F = void(float);
F& ir = *reinterpret_cast<F*>(&f); // undefined behavior:
// initializer refers to void(int) function
Примечания
| Макрос проверки поддержки функциональности | Значение | Стандарт | Функциональность |
|---|---|---|---|
__cpp_rvalue_references | 200610L | (C++11) | Ссылки на rvalue |
Отчеты об ошибках
Следующие отчеты об ошибках, изменяющие поведение, были применены ретроспективно к ранее опубликованным стандартам C++.
| DR | Применен к | Поведение согласно опубликованному стандарту | Корректное поведение |
|---|---|---|---|
| CWG 453 | C++98 | не было ясно, с каким объектом или функцией нельзя связать ссылку | уточнено |
| CWG 1510 | C++11 | cv-квалифицированные ссылки нельзя было сформировать в операнде decltype | разрешено |
| CWG 2550 | C++98 | параметры могли иметь тип «ссылка на void» | запрещено |
| CWG 2933 | C++98 | поведение при обращении к висячим ссылкам было неясным | уточнено |
Внешние ссылки
| Thomas Becker, 2013 — Объяснение ссылок на rvalue в C++ |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/reference