Jump to content

Справочник (C++)

В 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
  1. ^ ISO/IEC 14822 , пункт 9.3.3.2, параграф 1.
  2. ^ Саттер, Херб; Страуструп, Бьерн; Два короля, Габриэль. «Переадресация ссылок» (PDF) .
  3. ^ ISO/IEC 14822 , пункт 13.10.2.1, параграф 3.
  4. ^ Беккер, Томас. «Объяснение ссылок Rvalue в C++» . Проверено 25 ноября 2022 г.
[ редактировать ]
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 47b32a9a1d7256e7a68cc9558de34e4f__1723467960
URL1:https://arc.ask3.ru/arc/aa/47/4f/47b32a9a1d7256e7a68cc9558de34e4f.html
Заголовок, (Title) документа по адресу, URL1:
Reference (C++) - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)