Spec-Zone.ru › C++

Объявление ссылки

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

Синтаксис

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

& 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) список атрибутов

Ссылка должна быть инициализирована так, чтобы ссылаться на допустимый объект или функцию: см. инициализацию ссылки.

Тип «ссылка на (возможно, 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&&

(Это правило вместе со специальными правилами выведения аргументов шаблона в случаях, когда в шаблоне функции используется 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";           // 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)
}

Это позволяет автоматически выбирать подходящие конструкторы перемещения, операторы присваивания перемещением и другие функции с поддержкой перемещения (например, std::vector::push_back()).

Поскольку ссылки на 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());

Пересылающие ссылки

Пересылающие ссылки — это особый вид ссылок, сохраняющих категорию значения аргумента функции, что позволяет пересылать его с помощью std::forward. Пересылающей ссылкой является:

1) параметр функции шаблона, объявленный как ссылка на rvalue на не cv-квалифицированный параметр шаблона типа этого же шаблона функции:
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)

См. также выведение аргументов шаблона и std::forward.

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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API