Reference initialization
Привязывает ссылку к объекту.
Содержание |
Синтаксис
Несписочная инициализация
T
&
ref
=
target
;
T
|
(1) | ||||||||
T
&&
ref
=
target
;
T
|
(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
|
(1) | ||||||||
T
&&
ref
= {
arg1
,
arg2
,
...
};
T
|
(2) | ||||||||
func-refpar
({
arg1
,
arg2
,
...
});
|
(3) | ||||||||
` (сохранен без изменений)
- Символов и синтаксических конструкций C++
- Нумерации (1), (2), (3)
Содержимое тегов `
` представляет собой имена переменных и функций в C++, которые не подлежат переводу согласно требованиям.
Инициализация списком с указанием элементов (начиная с C++20)
T
&
ref
= {.
des1
=
arg1
, .
des2
{
arg2
}
...
};
T
|
(1) | ||||||||
T
&&
ref
= {.
des1
=
arg1
, .
des2
{
arg2
}
...
};
T
|
(2) | ||||||||
func-refpar
({.
des1
=
arg1
, .
des2
{
arg2
}
...
});
|
(3) | ||||||||
` также сохранены без изменений.
Ссылка на
T
может быть инициализирована объектом типа
T
, функцией типа
T
или объектом, неявно преобразуемым в
T
. После инициализации ссылку нельзя перенаправить (изменить) для ссылки на другой объект.
Ссылки инициализируются в следующих ситуациях:
Объяснение
| 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
:
-
Для неквалифицированных cv-версий
T1иT2, обозначаемых какU1иU2соответственно, еслиU1является подобным дляU2, илиU1является базовым классом дляU2, тоT1является ссылочно-связанным сT2. -
Если prvalue типа «указатель на
T2» может быть преобразовано в тип «указатель наT1» с помощью стандартной последовательности преобразований, тоT1является ссылочно-совместимым сT2.
Правила инициализации
|
Если инициализация ссылки использует обычную или назначенную (начиная с C++20) list-инициализацию, применяются правила list-инициализации . |
(начиная с C++11) |
Для несписочной инициализации ссылки, при заданном типе
target
как
U
, ссылка либо
связывается напрямую
с
target
, либо связывается со значением типа
T
, преобразованным из
target
. Сначала рассматривается прямое связывание, затем косвенное; если ни один из вариантов связывания недоступен, программа является некорректной.
Во всех случаях, когда совместимое по ссылке отношение двух типов используется для установления действительности привязки ссылки, и стандартная последовательность преобразований была бы некорректно сформирована, программа, требующая такой привязки, является некорректно сформированной.
Прямое связывание
Если выполняются все следующие условия:
- Инициализируемая ссылка является lvalue-ссылкой.
- target является не- битовым полем lvalue.
-
Tявляется ссылочно-совместимым сU.
Затем ссылка связывается с target или с соответствующим подобъектом базового класса:
double d = 2.0; double& rd = d; // rd ссылается на d const double& rcd = d; // rcd ссылается на d struct A {}; struct B : A {} b; A& ra = b; // ra ссылается на подобъект A в b const A& rca = b; // rca ссылается на подобъект A в b
В противном случае, если выполняются все следующие условия:
- Инициализируемая ссылка является lvalue-ссылкой.
-
Uявляется типом класса. -
Tне является ссылочно-связанным сU. -
target
может быть преобразован в lvalue типа
Vтаким образом, чтоTявляется ссылочно-совместимым сV.
Затем ссылка связывается с lvalue-результатом преобразования, или с его соответствующим базовым подобъектом класса:
struct A {}; struct B : A { operator int&(); }; int& ir = B(); // ir ссылается на результат B::operator int&
В противном случае, если инициализируемая ссылка является lvalue-ссылкой, и
T
не является const-квалифицированной или является volatile-квалифицированной, программа является некорректной:
double& rd2 = 2.0; // ошибка: не lvalue и ссылка не const int i = 2; double& rd3 = i; // ошибка: несоответствие типов и ссылка не const
В противном случае, если выполняются все следующие условия:
- target — это значение любой из следующих категорий:
|
(до C++11) |
|
(начиная с C++11)
(до C++17) |
|
(начиная с C++17) |
-
Tявляется ссылочно-совместимым сU.
Затем ссылка связывается с target , или с соответствующим подобъектом базового класса:
struct A {}; struct B : A {}; extern B f(); const A& rca2 = f(); // привязывается к подобъекту A rvalue типа B A&& rra = f(); // то же самое, что и выше int i2 = 42; int&& rri = static_cast<int&&>(i2); // привязывается напрямую к i2
|
Если
target
является prvalue,
материализация временного объекта
применяется к нему, при этом тип prvalue считается скорректированным типом
В этом случае ссылка связывается с результирующим объектом или с соответствующим подобъектом базового класса. |
(начиная с C++17) |
В противном случае, если выполняются все следующие условия:
-
Uявляется типом класса. -
Tне является ссылочно-связанным сU. -
target
может быть преобразован в значение
v
типа
Vтаким образом, чтоTявляется ссылочно-совместимым сV, где v принадлежит любой из следующих категорий:
|
(до C++11) |
|
(начиная с C++11)
(до C++17) |
|
(начиная с C++17) |
Затем ссылка связывается с результатом преобразования, или с соответствующим базовым подобъектом:
struct A {}; struct B : A {}; struct X { operator B(); } x; const A& r = x; // привязывается к подобъекту A результата преобразования B&& rrb = x; // привязывается напрямую к результату преобразования
|
Если результатом преобразования является prvalue,
временная материализация
применяется к нему, при этом тип prvalue считается скорректированным типом
В этом случае ссылка связывается с результирующим объектом или с соответствующим подобъектом базового класса. |
(начиная с C++17) |
Косвенная привязка
Если прямое связывание недоступно, рассматривается косвенное связывание. В этом случае,
T
не может быть ссылочно-связан с
U
.
Если
T
или
U
является классом, пользовательские преобразования рассматриваются по правилам
копирующей инициализации
объекта типа
T
с помощью пользовательского преобразования. Программа является некорректной, если соответствующая нессылочная копирующая инициализация была бы некорректной. Результат вызова функции преобразования, как описано для нессылочной
копирующей инициализации
, затем используется для прямой инициализации ссылки. Для этой прямой инициализации пользовательские преобразования не рассматриваются.
|
В противном случае создается временный объект типа
|
(до C++17) |
|
В противном случае
target
неявно преобразуется в prvalue типа "cv-unqualified
|
(начиная с C++17) |
const std::string& rs = "abc"; // rs ссылается на временный объект, копически инициализированный из символьного массива const double& rcd2 = 2; // rcd2 ссылается на временный объект со значением 2.0 int i3 = 2; double&& rrd3 = i3; // rrd3 ссылается на временный объект со значением 2.0
Время жизни временного объекта
Всякий раз, когда ссылка привязывается к временному объекту или его подобъекту, время жизни временного объекта продлевается до времени жизни ссылки (см. исключения времени жизни временных объектов ), где временный объект или его подобъект обозначается одним из следующих выражений:
|
(до C++17) |
|
(начиная с C++17) |
- выражение в скобках ( e ) , где e является одним из этих выражений,
- встроенное выражение индексации (built-in subscript expression) вида a [ n ] или n [ a ] , где a является массивом и одним из этих выражений,
- выражение доступа к члену класса (class member access expression) вида e. m , где e является одним из этих выражений, а m обозначает нестатический член данных объектного типа,
- операция доступа по указателю на член (pointer-to-member operation) вида e. * mp , где e является одним из этих выражений, а mp является указателем на член данных,
-
преобразование
const_cast,static_cast,dynamic_castилиreinterpret_castбез пользовательского преобразования, которое преобразует одно из этих выражений в glvalue, ссылающееся на объект, обозначенный операндом, или на его полный объект или подобъект ( явное приведение типа интерпретируется как последовательность таких преобразований), - условное выражение (conditional expression) вида cond ? e1 : e2 , являющееся glvalue, где e1 или e2 является одним из этих выражений, или
- встроенное выражение с запятой (built-in comma expression) вида x, e , являющееся glvalue, где e является одним из этих выражений.
Существуют следующие исключения из этого правила времени жизни:
|
(до C++26) |
- временный объект, привязанный к ссылочному параметру в вызове функции, существует до конца полного выражения, содержащего этот вызов функции: если функция возвращает ссылку, которая переживает полное выражение, она становится висячей ссылкой.
|
(since C++11) |
struct A { int&& r; }; A a1{7}; // OK, lifetime is extended A a2(7); // well-formed, but dangling reference |
(since C++20) |
В общем случае время жизни временного объекта не может быть дополнительно продлено путем «передачи его дальше»: вторая ссылка, инициализированная из переменной-ссылки или элемента данных, к которой был привязан временный объект, не влияет на его время жизни.
Примечания
Ссылки объявляются без инициализаторов только в объявлении параметров функции, в объявлении возвращаемого типа функции, в объявлении члена класса и с
extern
спецификатором.
До разрешения CWG issue 1696 , временный объект разрешалось привязывать к ссылочному члену в initializer list конструктора, и он сохранялся только до выхода из конструктора, а не в течение всего времени существования объекта. Такая инициализация стала некорректной после CWG 1696 , хотя многие компиляторы всё ещё поддерживают её (заметным исключением является clang).
Пример
#include <sstream> #include <utility> struct S { int mi; const std::pair<int, int>& mp; // ссылочный член }; void foo(int) {} struct A {}; struct B : A { int n; operator int&() { return n; } }; B bar() { return B(); } //int& bad_r; // ошибка: нет инициализатора extern int& ext_r; // OK int main() { // Lvalue int n = 1; int& r1 = n; // lvalue ссылка на объект n const int& cr(n); // ссылка может быть более cv-квалифицированной volatile int& cv{n}; // может использоваться любой синтаксис инициализации int& r2 = r1; // другая lvalue ссылка на объект n // int& bad = cr; // ошибка: менее cv-квалифицированная int& r3 = const_cast<int&>(cr); // требуется const_cast void (&rf)(int) = foo; // lvalue ссылка на функцию int ar[3]; int (&ra)[3] = ar; // lvalue ссылка на массив B b; A& base_ref = b; // ссылка на базовый подобъект int& converted_ref = b; // ссылка на результат преобразования // Rvalues // int& bad = 1; // ошибка: нельзя привязать lvalue ссылку к rvalue const int& cref = 1; // привязана к rvalue int&& rref = 1; // привязана к rvalue const A& cref2 = bar(); // ссылка на A подобъект временного объекта B A&& rref2 = bar(); // аналогично int&& xref = static_cast<int&&>(n); // прямая привязка к n // int&& copy_ref = n; // ошибка: нельзя привязать к lvalue double&& copy_ref = n; // привязка к временному rvalue со значением 1.0 // Ограничения времени жизни временных объектов // std::ostream& buf_ref = std::ostringstream() << 'a'; // временный ostringstream был привязан к левому операнду // operator<<, но его время жизни закончилось на точке с запятой, // поэтому buf_ref является висячей ссылкой S a {1, {2, 3}}; // временный pair {2, 3} привязан к ссылочному члену // a.mp, и его время жизни продлено до соответствия // времени жизни объекта a S* p = new S{1, {2, 3}}; // временный pair {2, 3} привязан к ссылочному // члену p->mp, но его время жизни закончилось на точке с запятой // p->mp является висячей ссылкой delete p; // Имитация [[maybe_unused]] примененного к следующим переменным: [](...){} ( 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 |
преобразование из
target
классового типа в другой
совместимый по ссылке тип могло быть только неявным |
разрешить явные
преобразования |
| CWG 1295 | C++11 | ссылка могла связываться с xvalue битового поля | запрещено |
| CWG 1299 | C++98 | определение временного объекта было неясным | прояснено |
| CWG 1571 | C++98 |
пользовательские преобразования при косвенной
привязке не учитывали тип target |
учитывается |
| CWG 1604 | C++98 | пользовательские преобразования не учитывались при косвенной привязке | учитываются |
| CWG 2352 | C++98 | совместимость по ссылке не учитывала квалификационные преобразования | учитывается |
| CWG 2481 | C++17 |
cv-квалификация не добавлялась к результирующему типу
материализации временного объекта при косвенной привязке |
добавлена |
| CWG 2657 | C++17 |
cv-квалификация не добавлялась к результирующему типу
материализации временного объекта при прямой привязке |
добавлена |
| CWG 2801 | C++98 | ссылочно-связанные типы были разрешены для косвенной привязки | запрещено |