Справочник (C++)
Эта статья нуждается в дополнительных цитатах для проверки . ( ноябрь 2013 г. ) |
В C++ языке программирования ссылка — это простой ссылочный тип данных, который менее мощный, но более безопасный, чем тип указателя, от C. унаследованный Название «Ссылка C++» может вызвать путаницу, поскольку в информатике ссылка представляет собой общий концептуальный тип данных, а указатели и ссылки C++ представляют собой конкретные реализации ссылочного типа данных. Определение ссылки в C++ таково, что ее существование не обязательно. Его можно реализовать как новое имя для существующего объекта (аналогично ключевому слову переименования в Ada).
Синтаксис и терминология
[ редактировать ]Декларация формы:
<Type>& <Name>
где <Type>
это тип и <Name>
определяет Говорят, что идентификатор идентификатор, тип которого является lvalue . ссылкой на <Type>
. [ 1 ]
Примеры:
int a = 5;
int& r_a = a;
extern int& r_b;
Здесь, r_a
и r_b
имеют тип «ссылка lvalue на int
"
int& Foo();
Foo
— это функция, которая возвращает ссылку «lvalue на int
"
void Bar(int& r_p);
Bar
— это функция со ссылочным параметром, который представляет собой «ссылку lvalue на int
"
class MyClass { int& m_b; /* ... */ };
MyClass
это class
с членом, который является ссылкой lvalue на int
int FuncX() { return 42 ; };
int (&f_func)() = FuncX;
int (&&f_func2)() = FuncX; // essentially equivalent to the above
FuncX
это функция, которая возвращает (не ссылочный тип) int
и f_func
это псевдоним для FuncX
const int& ref = 65;
const int& ref
является ссылкой lvalue на const int
указывая на часть памяти, имеющую значение 65.
int arr[3];
int (&arr_lvr)[3] = arr;
int (&&arr_rvr)[3] = std::move(arr);
typedef int arr_t[3];
int (&&arr_prvl)[3] = arr_t{}; // arr_t{} is an array prvalue
int *const & ptr_clv = arr; // same as int *const & ptr_clv = &arr[0];
int *&& ptr_rv = arr;
// int *&arr_lv = arr; // Error: Initializing an lvalue reference to non-const type with an rvalue
arr_lvr
является ссылкой на массив. При инициализации ссылки на массив преобразование массива в указатель не происходит, но происходит при инициализации ссылки на указатель. Поскольку преобразование массива в указатель возвращает значение prvalue, только lvalue ссылается на const
и ссылки rvalue могут быть инициализированы его результатом. Аналогично, при инициализации ссылки на функцию преобразование функции в указатель не происходит (см. f_func
выше), но это происходит при инициализации ссылки на указатель функции:
int FuncX() { return 42 ; };
int (*const &pf_func)() = FuncX; // same as int (*const &pf_func)() = &FuncX;
int (* &&pf_func2)() = FuncX;
Декларация формы:
<Type>&& <Name>
где <Type>
это тип и <Name>
Говорят, что идентификатор определяет идентификатор, тип которого является ссылкой rvalue на <Type>
.
Поскольку имя ссылки rvalue само по себе является lvalue, std::move
должен использоваться для передачи ссылки rvalue в перегрузку функции, принимающую параметр ссылки rvalue. Rvalue ссылается на параметры шаблона неквалифицированного типа cv того же шаблона функции или auto&&
за исключением случаев, когда они выводятся из списка инициализаторов, заключенного в фигурные скобки, называются ссылками пересылки (в некоторых старых источниках они называются «универсальными ссылками»). [ 2 ] ) и могут действовать как ссылки lvalue или rvalue в зависимости от того, что им передается. [ 3 ]
Когда они встречаются в параметрах функции, они иногда используются с std::forward
для пересылки аргумента функции в другую функцию, сохраняя при этом категорию значения (lvalue или rvalue), которую он имел при передаче вызывающей функции. [ 4 ]
Типы, которые являются своего рода «ссылкой на <Type>
" иногда называют ссылочными типами . Идентификаторы ссылочного типа называются ссылочными переменными . Однако называть их переменными на самом деле, как мы увидим, на самом деле неправильно.
Ссылки не являются объектами и могут ссылаться только на типы объектов или функций. Массивы ссылок, указатели на ссылки и ссылки на ссылки не допускаются, поскольку для них требуются типы объектов. int& i[4]
, int&*i
и int& &i
вызовет ошибки компиляции (в то время как int(& i)[4]
(ссылка на массив) и int*&i
(ссылка на указатель) не будет предполагать, что они инициализированы). Ссылки на void
также плохо сформированы, потому что void
не является типом объекта или функции, а является ссылкой на void *
может существовать.
Объявление ссылок как константных или изменчивых( int& volatile i
) также завершается сбоем, если не используется typedef/decltype, в этом случае константа/летучая игнорируется. Однако, если происходит вычисление аргументов шаблона и выводится ссылочный тип (что происходит, когда используются пересылающие ссылки и в функцию передается lvalue) или если typedef
, using
или decltype
обозначают ссылочный тип, можно взять ссылку на этот тип. В этом случае правило, используемое для определения типа ссылки, называется свертыванием ссылок и работает следующим образом: T
и ссылочный тип на T
TR
, пытаясь создать ссылку rvalue на TR
создает TR
в то время как ссылка lvalue на TR
создает ссылку lvalue на T
. Другими словами, ссылки lvalue переопределяют ссылки rvalue, а ссылки rvalue на ссылки rvalue остаются неизменными.
int i;
typedef int& LRI;
using RRI = int&&;
LRI& r1 = i; // r1 has the type int&
const LRI& r2 = i; // r2 has the type int&
const LRI&& r3 = i; // r3 has the type int&
RRI& r4 = i; // r4 has the type int&
RRI&& r5 = 5; // r5 has the type int&&
decltype(r2)& r6 = i; // r6 has the type int&
decltype(r2)&& r7 = i; // r7 has the type int&
Нестатическую функцию-член можно объявить с помощью квалификатора ref. Этот квалификатор участвует в разрешении перегрузки и применяется к неявному параметру объекта, например const
и volatile
но в отличие от этих двух, он не меняет свойств this
. Он требует, чтобы функция вызывалась для экземпляра класса lvalue или rvalue.
#include <iostream>
struct A
{
A() = default;
void Print()const& { std::cout << "lvalue\n"; }
void Print()const&& { std::cout << "rvalue\n"; }
};
int main()
{
A a;
a.Print(); // prints "lvalue"
std::move(a).Print(); // prints "rvalue"
A().Print(); // prints "rvalue"
A&& b = std::move(a);
b.Print(); // prints "lvalue"(!)
}
Связь с указателями
[ редактировать ]Ссылки C++ отличаются от указателей по нескольким важным признакам:
- Ссылка сама по себе не является объектом, это псевдоним; любое появление его имени относится непосредственно к объекту, на который оно ссылается. Объявление указателя создает объект указателя, отличный от объекта, на который ссылается указатель.
- Контейнеры ссылок не допускаются, поскольку ссылки не являются объектами, а контейнеры указателей являются обычным явлением для полиморфизма.
- Не допускается создавать ссылку на ссылку, поскольку ссылки могут ссылаться только на объекты (или функции).
- Ссылки не могут быть неинициализированы. Поскольку повторно инициализировать ссылку невозможно, ее необходимо инициализировать сразу после создания. В * Как только ссылка создана, ее нельзя впоследствии использовать для ссылки на другой объект; его нельзя переустановить . Часто это делается с помощью указателей.
- Ссылки не могут иметь значение null , тогда как указатели могут; каждая ссылка ссылается на некоторый объект, хотя она может быть допустимой, а может и нет.
в частности, локальные и глобальные переменные должны быть инициализированы там, где они определены, а ссылки, являющиеся членами данных экземпляров класса, должны быть инициализированы в списке инициализаторов конструктора класса. Например:
int& k; // compiler will complain: error: `k' declared as reference but not initialized
Существует простое преобразование между указателями и ссылками: оператор адреса ( &
) даст указатель, ссылающийся на тот же объект при применении к ссылке, и ссылку, которая инициализируется из разыменования ( *
) значения указателя будет ссылаться на тот же объект, что и этот указатель, если это возможно без вызова неопределенного поведения. Эта эквивалентность является отражением типичной реализации, которая эффективно компилирует ссылки в указатели, которые неявно разыменовываются при каждом использовании. Хотя обычно это так, стандарт C++ не обязывает компиляторы реализовывать ссылки с использованием указателей.
Следствием этого является то, что во многих реализациях работа с переменной с автоматическим или статическим временем жизни через ссылку, хотя синтаксически аналогична прямому доступу к ней, может включать в себя скрытые операции разыменования, которые являются дорогостоящими.
Кроме того, поскольку операции со ссылками настолько ограничены, их гораздо легче понять, чем указатели, и они более устойчивы к ошибкам. Хотя указатели можно сделать недействительными с помощью различных механизмов, начиная от переноса нулевого значения и заканчивая арифметическими действиями за пределами границ, незаконными приведениями и созданием их из произвольных целых чисел, ранее действительная ссылка становится недействительной только в двух случаях:
- Если он относится к объекту с автоматическим выделением, который выходит за рамки,
- Если он относится к объекту внутри освобожденного блока динамической памяти.
Первый легко обнаружить автоматически, если ссылка имеет статическую область видимости, но он по-прежнему является проблемой, если ссылка является членом динамически выделяемого объекта; второй обнаружить труднее. Это единственные проблемы со ссылками, которые можно решить с помощью разумной политики распределения.
Использование ссылок
[ редактировать ]- Помимо полезной замены указателей, ссылки еще одним удобным применением являются списки параметров функций, где они позволяют передавать параметры, используемые для вывода, без явного получения адреса вызывающей стороной. Например:
void Square(int x, int& out_result) {
out_result = x * x;
}
Затем следующий вызов поместит 9 в y :
int y;
Square(3, y);
Однако следующий вызов приведет к ошибке компилятора, поскольку ссылочные параметры lvalue не уточнены с помощью const
может быть привязан только к адресуемым значениям:
Square(3, 6);
- Возврат ссылки lvalue позволяет назначать вызовы функций:
int& Preinc(int& x) { return ++x; // "return x++;" would have been wrong } Preinc(y) = 5; // same as ++y, y = 5
- Во многих реализациях обычные механизмы передачи параметров часто подразумевают дорогостоящую операцию копирования для больших параметров. Ссылки, квалифицированные с
const
являются полезным способом передачи больших объектов между функциями, позволяющим избежать этих накладных расходов:void FSlow(BigObject x) { /* ... */ } void FFast(const BigObject& x) { /* ... */ } BigObject y; FSlow(y); // Slow, copies y to parameter x. FFast(y); // Fast, gives direct read-only access to y.
Если FFast
на самом деле требуется собственная копия x , которую он может изменять, он должен явно создать копию. Хотя тот же метод можно применить и с использованием указателей, это потребует изменения каждого места вызова функции, чтобы добавить громоздкий адрес ( &
) операторы аргумента, и их будет так же сложно отменить, если впоследствии объект станет меньше.
Полиморфное поведение
[ редактировать ]Продолжая связь между ссылками и указателями (в контексте C++), первые демонстрируют полиморфные возможности, как и следовало ожидать:
#include <iostream>
class A {
public:
A() = default;
virtual void Print() { std::cout << "This is class A\n"; }
};
class B : public A {
public:
B() = default;
virtual void Print() { std::cout << "This is class B\n"; }
};
int main() {
A a;
A& ref_to_a = a;
B b;
A& ref_to_b = b;
ref_to_a.Print();
ref_to_b.Print();
}
Приведенный выше источник является допустимым C++ и генерирует следующий вывод:
This is class A
This is class B
Ссылки
[ редактировать ]- ^ ISO/IEC 14822 , пункт 9.3.3.2, параграф 1.
- ^ Саттер, Херб; Страуструп, Бьерн; Два короля, Габриэль. «Переадресация ссылок» (PDF) .
- ^ ISO/IEC 14822 , пункт 13.10.2.1, параграф 3.
- ^ Беккер, Томас. «Объяснение ссылок Rvalue в C++» . Проверено 25 ноября 2022 г.
Внешние ссылки
[ редактировать ]- Ссылки в C++ FAQ Lite
- Пояснения к ссылкам на пересылку
- Объединенный технический комитет ISO/IEC JTC 1, подкомитет SC 22, рабочая группа WG 21. Международный стандарт ISO/IEC 14822 (PDF) .
{{cite book}}
:|work=
игнорируется ( помощь ) CS1 maint: несколько имен: список авторов ( ссылка ) CS1 maint: числовые имена: список авторов ( ссылка ) .