Spec-Zone.ru › C++

Инициализация ссылок

Связывает ссылку с объектом.

Синтаксис

Инициализация без списков
T & ref = target ;

T & ref ( target );

(1)
T && ref = target ;

T && ref ( target );

(2) (с C++11)
func-refpar ( target ) (3)
return target ; (4) (внутри определения func-refret )
Class::Class(...) : ref-member ( target ) { ... } (5) (внутри определения Class )
Инициализация списками (с C++11)
T & ref = { arg1, arg2, ... };

T & ref { arg1, arg2, ... };

(1)
T && ref = { arg1, arg2, ... };

T && ref { arg1, arg2, ... };

(2)
func-refpar ({ arg1, arg2, ... }); (3)
Инициализация списками с указанием элементов (с C++20)
T & ref = {.des1 = arg1 , .des2 { arg2 } ... };

T & ref {.des1 = arg1 , .des2 { arg2 } ... };

(1)
T && ref = {.des1 = arg1 , .des2 { arg2 } ... };

T && ref {.des1 = arg1 , .des2 { arg2 } ... };

(2)
func-refpar ({.des1 = arg1 , .des2 { arg2 } ... }); (3)

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

Ссылки инициализируются в следующих ситуациях:

1) При объявлении переменной-ссылки lvalue со значениями по умолчанию.
2) При объявлении переменной-ссылки rvalue со значениями по умолчанию.
3) В выражении вызова функции, когда параметр функции имеет тип ссылки.
4) В операторе return, когда функция возвращает тип ссылки.
5) При инициализации нестатического члена-ссылки с помощью инициализатора члена.

Объяснение

T - тип ссылаемого объекта
ref - переменная-ссылка, подлежащая инициализации
target - выражение инициализации
func-refpar - функция с параметром типа ссылка (T & или T &&(с C++11))
func-refret - функция, возвращающая ссылку (T & или T &&(с C++11))
Class - имя класса
ref-member - нестатический член-ссылка (T & или T &&(с C++11)) класса Class
des1, des2, ... - указатели
arg1, arg2, ... - значения в списках инициализаций

Определения

Для двух типов T1 и T2:

  • Учитывая неквалифицированные версии T1 и T2 как U1 и U2 соответственно, если U1 является совместимым с U2, или U1 является базовым классом U2, T1 является родственной ссылкой для T2.
  • Если prvalue типа «указатель на T2» может быть преобразован в тип «указатель на T1» через стандартную цепочку преобразований, T1 является совместимой ссылкой с T2.

Правила инициализации

Если инициализация ссылки использует обычную или обозначенную(с C++20) инициализацию списком, применяются правила инициализации списком.

(с C++11)

Для несписочной инициализации ссылки, учитывая тип target как U, ссылка либо связывается напрямую с target, либо связывается со значением типа T, преобразованного из target. Прямое связывание рассматривается в первую очередь, за которым следует косвенное связывание; если ни один из способов связывания недоступен, программа является ошибочной.

Во всех случаях, когда для установления корректности связывания ссылки используется отношение совместимости типов ссылок, а стандартная цепочка преобразований будет некорректной, программа, которая требует такого связывания, является ошибочной.

Прямое связывание

Если выполнены все следующие условия:

  • Ссылка, подлежащая инициализации, является ссылкой на lvalue.
  • target — это ссылка на lvalue, не являющаяся полем-битом.
  • T совместима со ссылкой на U.

Затем ссылка связывается с target или с соответствующим подобъектом базового класса:

double d = 2.0;
double& rd = d;        // rd refers to d
const double& rcd = d; // rcd refers to d
 
struct A {};
struct B : A {} b;
 
A& ra = b;             // ra refers to A subobject in b
const A& rca = b;      // rca refers to A subobject in b

В противном случае, если выполняются все следующие условия:

  • Ссылка, подлежащая инициализации, является ссылкой на lvalue.
  • U — это тип класса.
  • T не является ссылкой на U.
  • target может быть преобразована в lvalue типа V, при этом T совместима со ссылкой на V.

Затем ссылка связывается с результатом преобразования lvalue или с соответствующим подобъектом базового класса:

struct A {};
struct B : A { operator int&(); };
 
int& ir = B(); // ir refers to the result of B::operator int&

В противном случае, если ссылка, подлежащая инициализации, является ссылкой на lvalue, и T не является const-квалифицированной или является volatile-квалифицированной, программа является некорректной:

double& rd2 = 2.0; // error: not an lvalue and reference is not const
int i = 2;
double& rd3 = i;   // error: type mismatch and reference is not const

В противном случае, если выполняются все следующие условия:

  • target имеет любое из следующих значений:
  • rvalue
(до C++11)
  • non-bit-field xvalue
  • class prvalue
  • array prvalue
  • function lvalue
(начиная с C++11)
(до C++17)
  • non-bit-field rvalue
  • function lvalue
(начиная с C++17)
  • T совместима со ссылкой на U.

Затем ссылка связывается с target или с соответствующим подобъектом базового класса:

struct A {};
struct B : A {};
extern B f();
 
const A& rca2 = f(); // bound to the A subobject of the B rvalue.
A&& rra = f();       // same as above
 
int i2 = 42;
int&& rri = static_cast<int&&>(i2); // bound directly to i2

Если target является prvalue, к нему применяется материализация временного объекта. Ссылка связывается с результирующим объектом или с соответствующим подобъектом базового класса.

(начиная с C++17)

В противном случае, если выполняются все следующие условия:

  • U — это тип класса.
  • T не является ссылкой на U.
  • target может быть преобразовано в значение v типа V, при этом T совместима со ссылкой на V, где v имеет любой из следующих типов:
  • rvalue
(до C++11)
  • xvalue
  • class prvalue
  • function lvalue
(начиная с C++11)
(до C++17)
  • rvalue
  • function lvalue
(начиная с C++17)

Затем ссылка связывается с результатом преобразования или с соответствующим подобъектом базового класса:

struct A {};
struct B : A {};
struct X { operator B(); } x;
 
const A& r = x; // bound to the A subobject of the result of the conversion
B&& rrb = x;    // bound directly to the result of the conversion

Если результатом преобразования является prvalue, его тип C корректируется добавлением cv-квалификаторов T, и к нему применяется материализация временного объекта. Ссылка связывается с результирующим объектом или с соответствующим подобъектом базового класса.

(начиная с C++17)

Непрямая привязка

Если прямая привязка недоступна, рассматривается непрямая привязка. В этом случае T не может быть ссылкой на U.

Если T или U — это тип класса, рассматриваются пользовательские преобразования в соответствии с правилами копирующей инициализации объекта типа T с помощью пользовательского преобразования. Программа является некорректной, если соответствующая копирующая инициализация без ссылок была бы некорректной. Затем результат вызова функции преобразования, как описано для копирующей инициализации без ссылок, используется для прямой инициализации ссылки. Для этой прямой инициализации пользовательские преобразования не рассматриваются.

В противном случае создается временный объект типа T и инициализируется копированием из target. Затем ссылка привязывается к временному объекту.

(до C++17)

В противном случае target неявно преобразуется в prvalue типа «неквалифицированный T». Применяется преобразование материализации временного объекта, учитывая тип prvalue как T, и ссылка привязывается к результирующему объекту.

(начиная с C++17)
const std::string& rs = "abc"; // rs refers to temporary copy-initialized from char array
const double& rcd2 = 2;        // rcd2 refers to temporary with value 2.0
int i3 = 2;
double&& rrd3 = i3;            // rrd3 refers to temporary with value 2.0

Срок жизни временного объекта

Всякий раз, когда ссылка привязывается к временному объекту или его подобъекту, срок жизни временного объекта продлевается до срока жизни ссылки (см. исключения сроков жизни временных объектов), где временный объект или его подобъект обозначается одним из следующих выражений:

  • выражение prvalue типа объекта,
(до C++17)
  • преобразование материализации временного объекта,
(начиная с C++17)
  • скобочное выражение (e), где e — одно из этих выражений,
  • встроенное выражение подстрочного индекса формата a[n] или n[a], где a — массив, и является одним из этих выражений,
  • выражение доступа к члену класса формата e.m, где e — одно из этих выражений, а m обозначает нестатический член данных типа объекта,
  • операция указателя на член формата e.*mp, где e — одно из этих выражений, а mp — указатель на член данных,
  • преобразование const_cast, static_cast, dynamic_cast или reinterpret_cast без пользовательского преобразования, которое преобразует одно из этих выражений в glvalue, ссылающееся на объект, указанный операндом, или на его полный объект или подобъект (выражение явного преобразования интерпретируется как последовательность таких преобразований),
  • условное выражение формата cond ? e1 : e2, которое является glvalue, где e1 или e2 — одно из этих выражений, или
  • встроенное выражение запятой формата x, e, которое является glvalue, где e — одно из этих выражений.

Существуют следующие исключения из этого правила о сроке жизни:

  • временный объект, привязанный к значению возврата функции в операторе return, не продлевается: он уничтожается сразу по окончании выражения возврата. Такой оператор return всегда возвращает висячую ссылку.
  • временный объект, привязанный к параметру-ссылке в вызове функции, существует до конца всего выражения, содержащего этот вызов функции: если функция возвращает ссылку, которая сохраняется дольше всего выражения, она становится висячей ссылкой.
  • временный объект, привязанный к ссылке в инициализаторе, используемом в выражении new, существует до конца всего выражения, содержащего это выражение new, а не до тех пор, пока не будет инициализирован объект. Если инициализированный объект продолжает существовать дольше всего выражения, его член-ссылка становится висячей ссылкой.
(начиная с C++11)
  • временный объект, привязанный к ссылке в элементе-ссылке агрегата, инициализированного с использованием синтаксиса прямой инициализации (скобки), существует до конца всего выражения, содержащего инициализатор, в отличие от синтаксиса инициализации списком {фигурные скобки}.
struct A
{
    int&& r;
};
 
A a1{7}; // OK, lifetime is extended
A a2(7); // well-formed, but dangling reference
(начиная с C++20)

В общем случае, срок существования временной переменной нельзя продлить, «передавая» ее: второй ссылкой, инициализированной из переменной-ссылки или члена данных, к которому была привязана временная переменная, ее срок существования не затрагивается.

Примечания

Ссылки без инициализаторов встречаются только в объявлении параметра функции, в объявлении возвращаемого типа функции, в объявлении члена класса и с спецификатором extern.

До разрешения проблемы CWG issue 1696, временная переменная может быть привязана к члену-ссылке в конструкторе списке инициализаторов, и она сохраняется только до завершения конструктора, а не до тех пор, пока существует объект. Такая инициализация некорректна начиная с CWG 1696, хотя многие компиляторы по-прежнему ее поддерживают (заметное исключение — clang).

Пример

#include <sstream>
#include <utility>
 
struct S
{
    int mi;
    const std::pair<int, int>& mp; // reference member
};
 
void foo(int) {}
 
struct A {};
 
struct B : A
{
    int n;
    operator int&() { return n; }
};
 
B bar() { return B(); }
 
//int& bad_r;      // error: no initializer
extern int& ext_r; // OK
 
int main()
{
//  Lvalues
    int n = 1;
    int& r1 = n;                    // lvalue reference to the object n
    const int& cr(n);               // reference can be more cv-qualified
    volatile int& cv{n};            // any initializer syntax can be used
    int& r2 = r1;                   // another lvalue reference to the object n
//  int& bad = cr;                  // error: less cv-qualified
    int& r3 = const_cast<int&>(cr); // const_cast is needed
 
    void (&rf)(int) = foo; // lvalue reference to function
    int ar[3];
    int (&ra)[3] = ar;     // lvalue reference to array
 
    B b;
    A& base_ref = b;        // reference to base subobject
    int& converted_ref = b; // reference to the result of a conversion
 
//  Rvalues
//  int& bad = 1;        // error: cannot bind lvalue ref to rvalue
    const int& cref = 1; // bound to rvalue
    int&& rref = 1;      // bound to rvalue
 
    const A& cref2 = bar(); // reference to A subobject of B temporary
    A&& rref2 = bar();      // same
 
    int&& xref = static_cast<int&&>(n); // bind directly to n
//  int&& copy_ref = n;                 // error: can't bind to an lvalue
    double&& copy_ref = n;              // bind to an rvalue temporary with value 1.0
 
//  Restrictions on temporary lifetimes
//  std::ostream& buf_ref = std::ostringstream() << 'a';
                     // the ostringstream temporary was bound to the left operand
                     // of operator<< but its lifetime ended at the semicolon so
                     // the buf_ref is a dangling reference
 
    S a {1, {2, 3}}; // temporary pair {2, 3} bound to the reference member
                     // a.mp and its lifetime is extended to match 
                     // the lifetime of object a
 
    S* p = new S{1, {2, 3}}; // temporary pair {2, 3} bound to the reference
                             // member p->mp, but its lifetime ended at the semicolon
                             // p->mp is a dangling reference
    delete p;
 
    // Imitate [[maybe_unused]] applied to the following variables:
    [](...){}
    (
        cv, r2, r3, rf, ra, base_ref, converted_ref,
        a, cref, rref, cref2, rref2, copy_ref, xref
    );
}

Отчеты об ошибках

Следующие отчеты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Применено к Поведение, опубликованное в Правильное поведение
CWG 391 C++98 инициализация ссылки с квалификатором const с типом класса
rvalue может создать временную переменную, и конструктор этого класса
был необходим для копирования rvalue во временную переменную
временная переменная
не создается, конструктор
не требуется
CWG 450 C++98 ссылка на массив с квалификатором const не могла быть
инициализирована с ссылкой-совместимым массивом rvalue
разрешено
CWG 589 C++98 ссылка не могла быть напрямую привязана к массиву или rvalue класса разрешено
CWG 656 C++98 ссылка с квалификатором const, инициализированная типом, который не
совместим со ссылкой, но имеет функцию преобразования в тип,
совместимый со ссылкой, была привязана к временной переменной,
скопированной из возвращаемого значения (или его подобъекта базового класса) функции преобразования
привязана к возвращаемому
значению (или его подобъекту
базового класса) напрямую
CWG 1287 C++11 преобразование из целевого типа класса в другой
совместимый со ссылкой тип могло быть только неявным
разрешить явные
преобразования
CWG 1295 C++11 ссылка могла быть привязана к битовому полю xvalue запрещено
CWG 1299 C++98 определение временной переменной было неясным уточнено
CWG 1571 C++98 пользовательские преобразования в косвенной
связывании не учитывали тип целевого
учтены
CWG 1604 C++98 пользовательские преобразования не учитывались в косвенной связывании учтены
CWG 2352 C++98 совместимость ссылок не учитывала преобразования квалификаций учтены
CWG 2481 C++17 cv-квалификация не была добавлена к типу результата
материализации временной переменной в косвенной связывании
добавлены
CWG 2801 C++98 типы, связанные со ссылками, были разрешены для косвенной связки запрещены

См. также

  • конструктор
  • конструктор преобразования
  • копирующая операция присваивания
  • копирующий конструктор
  • explicit
  • инициализация
    • постоянная инициализация
    • копирующая инициализация
    • прямая инициализация
    • инициализация списком
  • перемещающая операция присваивания
  • перемещающий конструктор
  • new

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/reference_initialization

Spec-Zone.ru

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