Reference declaration
Объявляет именованную переменную как ссылку, то есть псевдоним уже существующего объекта или функции.
Содержание |
Синтаксис
Объявление ссылочной переменной — это любое простое объявление, чей декларатор имеет форму
&
attr
(необязательно)
declarator
|
(1) | ||||||||
&&
attr
(необязательно)
declarator
|
(2) | (начиная с C++11) | |||||||
D
как
lvalue-ссылку
на тип, определяемый
decl-specifier-seq
S
.
D
как
rvalue-ссылку
на тип, определяемый
decl-specifier-seq
S
.
| declarator | - | любой декларатор за исключением декларатора ссылки, декларатора массива и декларатора указателя (не существует ссылок на ссылки, массивов ссылок или указателей на ссылки) |
| attr | - | (начиная с C++11) список атрибутов |
Ссылка должна быть инициализирована для ссылки на допустимый объект или функцию: смотрите reference initialization .
Тип «ссылка на (возможно, cv-квалифицированный) void » не может быть образован.
Ссылочные типы не могут быть
cv-квалифицированы
на верхнем уровне; не существует синтаксиса для этого в объявлении, и если квалификация добавляется к typedef-имени
или
decltype
спецификатору,
(начиная с C++11)
или
параметру шаблона типа
, она игнорируется.
Ссылки не являются объектами; они не обязательно занимают память, хотя компилятор может выделить память, если это необходимо для реализации требуемой семантики (например, нестатический член данных ссылочного типа обычно увеличивает размер класса на величину, необходимую для хранения адреса памяти).
Поскольку ссылки не являются объектами, не существует массивов ссылок, указателей на ссылки и ссылок на ссылки:
int& a[3]; // ошибка int&* p; // ошибка int& &r; // ошибка
Свёртывание ссылокРазрешено формировать ссылки на ссылки через манипуляции с типами в шаблонах или 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"; // изменяет s // r2 += "!"; // ошибка: нельзя изменять через ссылку на константу std::cout << r2 << '\n'; // выводит s, которая теперь содержит "Example" }
Они также могут использоваться для реализации семантики передачи по ссылке в вызовах функций:
#include <iostream> #include <string> void double_string(std::string& s) { s += s; // 's' является тем же объектом, что и 'str' в main() } 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() возвращает ссылку на char } int main() { std::string str = "Test"; char_number(str, 1) = 'a'; // вызов функции является lvalue, может быть присвоено значение std::cout << str << '\n'; }
Ссылки на rvalueСсылки на rvalue могут использоваться для продления времени жизни временных объектов (обратите внимание, что ссылки lvalue на const также могут продлевать время жизни временных объектов, но через них нельзя изменять объекты):
Запустить этот код
#include <iostream> #include <string> int main() { std::string s1 = "Test"; // std::string&& r1 = s1; // ошибка: нельзя привязать к lvalue const std::string& r2 = s1 + s1; // корректно: lvalue-ссылка на const продлевает время жизни // r2 += "Test"; // ошибка: нельзя модифицировать через ссылку на const std::string&& r3 = s1 + s1; // корректно: rvalue-ссылка продлевает время жизни r3 += "Test"; // корректно: можно модифицировать через ссылку на non-const std::cout << r3 << '\n'; } Что еще важнее, когда функция имеет как перегрузки с rvalue-ссылкой, так и с lvalue-ссылкой overloads , перегрузка с rvalue-ссылкой связывается с rvalues (включая как prvalues, так и xvalues), тогда как перегрузка с lvalue-ссылкой связывается с lvalues:
Запустить этот код
#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); // вызывает f(int&) f(ci); // вызывает f(const int&) f(3); // вызывает f(int&&) // вызвала бы f(const int&) если бы перегрузка f(int&&) не была предоставлена f(std::move(i)); // вызывает f(int&&) // переменные rvalue reference являются lvalues при использовании в выражениях int&& x = 1; f(x); // вызывает f(int& x) f(std::move(x)); // вызывает f(int&& x) } Это позволяет move конструкторам , move операторам присваивания и другим функциям, поддерживающим перемещение (например, std::vector::push_back() ), автоматически выбираться, когда это уместно. Поскольку ссылки на rvalue могут связываться с xvalue, они могут ссылаться на нетривиальные объекты: int i2 = 42; int&& rri = std::move(i2); // напрямую связывается с i2 Это позволяет переместить объект в области видимости, который больше не нужен: std::vector<int> v{1, 2, 3, 4, 5}; std::vector<int> v2(std::move(v)); // связывает ссылку на rvalue с v assert(v.empty()); Пересылающие ссылкиПересылающие ссылки — это особый вид ссылок, которые сохраняют категорию значения аргумента функции, делая возможным его пересылку с помощью std::forward . Пересылающие ссылки бывают двух видов:
1)
параметр функции шаблона функции, объявленный как rvalue-ссылка на cv-неквалифицированный
параметр шаблона типа
того же самого шаблона функции:
template<class T> int f(T&& x) // x - это forwarding reference { return g(std::forward<T>(x)); // и поэтому может быть forwarded } int main() { int i; f(i); // аргумент является lvalue, вызывает f<int&>(int&), std::forward<int&>(x) является lvalue f(0); // аргумент является rvalue, вызывает f<int>(int&&), std::forward<int>(x) является rvalue } template<class T> int g(const T&& x); // x не является forwarding reference: const T не является cv-unqualified template<class T> struct A { template<class U> A(T&& x, U&& y, int* p); // x не является forwarding reference: T не является // type template parameter конструктора, // но y является forwarding reference };
2)
auto
&&
за исключением случаев, когда тип выводится из списка инициализации в фигурных скобках
или, когда представляет параметр шаблона класса во время
вывода аргументов шаблона класса
(начиная с C++17)
:
auto&& vec = foo(); // foo() может быть lvalue или rvalue, vec является пересылающей ссылкой auto i = std::begin(vec); // работает в обоих случаях (*i)++; // работает в обоих случаях g(std::forward<decltype(vec)>(vec)); // пересылает, сохраняя категорию значения for (auto&& x: f()) { // x является пересылающей ссылкой; это распространенный способ использования range for в обобщенном коде } auto&& z = {1, 2, 3}; // *не* является пересылающей ссылкой (особый случай для списков инициализации) См. также template argument deduction и std::forward . |
(начиная с C++11) |
Висячие ссылки
Хотя ссылки всегда ссылаются на допустимые объекты или функции при инициализации, возможно создать программу, в которой время жизни объекта, на который ссылаются, заканчивается, но ссылка остается доступной ( висячая ).
Дано выражение expr ссылочного типа, и пусть target будет объектом или функцией, на которую указывает ссылка:
- Если указатель на target был бы валидным в контексте вычисления expr , результат обозначает target .
- В противном случае поведение не определено.
std::string& f() { std::string s = "Example"; return s; // выход из области видимости s: // вызывается его деструктор и освобождается память } std::string& r = f(); // висячая ссылка std::cout << r; // неопределенное поведение: чтение из висячей ссылки std::string s = f(); // неопределенное поведение: копирующая инициализация из висячей ссылки
Обратите внимание, что rvalue-ссылки и lvalue-ссылки на const продлевают время жизни временных объектов (см. Инициализация ссылок для правил и исключений).
Если объект, на который ссылаются, был уничтожен (например, путем явного вызова деструктора), но память не была освобождена, ссылка на объект вне времени жизни может использоваться ограниченными способами и может стать валидной, если объект будет воссоздан в той же памяти (см. Access outside of lifetime для подробностей).
Ссылки с недоступным типом
Попытка связать ссылку с объектом, где преобразованный инициализатор является lvalue (до C++11) glvalue (начиная с C++11) , через который объект не является type-accessible , приводит к неопределённому поведению:
char x alignas(int); int& ir = *reinterpret_cast<int*>(&x); // неопределённое поведение: // инициализатор ссылается на char-объект
Несовместимые по вызову ссылки
Попытка привязать ссылку к функции, когда преобразованный инициализатор является lvalue (до C++11) является glvalue (начиная с C++11) и его тип не совместим с вызовом с типом определения функции, приводит к неопределённому поведению:
void f(int); using F = void(float); F& ir = *reinterpret_cast<F*>(&f); // неопределённое поведение: // инициализатор ссылается на функцию void(int)
Примечания
| Макрос тестирования возможностей | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_rvalue_references
|
200610L
|
(C++11) | Rvalue references |
Отчеты о дефектах
Следующие отчеты об изменениях поведения, влияющие на дефекты, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Applied to | Behavior as published | Correct behavior |
|---|---|---|---|
| CWG 453 | C++98 | было неясно, к какому объекту или функции нельзя привязать ссылку | прояснено |
| CWG 1510 | C++11 | cv-квалифицированные ссылки не могли быть сформированы в операнде decltype | разрешено |
| CWG 2550 | C++98 | параметры могли иметь тип "ссылка на void " | запрещено |
| CWG 2933 | C++98 | поведение при доступе к висячим ссылкам было неясным | прояснено |
Внешние ссылки
| Томас Бекер, 2013 - C++ Rvalue References Explained |