Namespaces
Variants

Reference declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Объявляет именованную переменную как ссылку, то есть псевдоним уже существующего объекта или функции.

Содержание

Синтаксис

Объявление ссылочной переменной — это любое простое объявление, чей декларатор имеет форму

& attr  (необязательно) declarator (1)
&& attr  (необязательно) declarator (2) (начиная с C++11)
1) Объявитель lvalue-ссылки : объявление S & D ; объявляет D как lvalue-ссылку на тип, определяемый decl-specifier-seq S .
2) Декларатор rvalue-ссылки : объявление S && D ; объявляет 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&&

(Это, вместе с особыми правилами для вывода шаблонных аргументов когда T&& используется в шаблонах функций, формирует правила, которые делают возможной работу std::forward .)

(начиная с 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