Посилання (С++)
В мові програмування С++, посилання це простий тип даних, який менш потужній, але більш безпечний ніж вказівник.
Тип посилання іноді називається псевдонімом, і слугує для створення об’єкту додаткового імені. Посилання дозволяє маніпулювати об'єктом так само, як це робиться за допомогою вказівника, однак це не потребує спеціального синтаксису, яке необхідне при роботі з вказівниками. Зазвичай посилання використовуються як формальні параметри функцій.
Сама назва С++ посилання може приводити до плутанини, оскільки в інформатиці під посиланням розуміють узагальнений концептуальний тип, а вказівники і посилання в C++ є специфічними реалізаціями цього типу посилання.
Синтаксис і термінологія
Декларація посилання має наступну форму:
<Type> & <Name>
Де <тип>
це тип даних, а <ім’я>
це ідентифікатор змінної, який містить в собі посилання на тип змінної <тип>
.
Приклади:
int A = 5;
int& rA = A;
extern int& rB;
int& foo ();
void bar (int& rP);
class MyClass { int& m_b; /* ... */ };
int funcX() { return 42 ; }; int (&xFunc)() = funcX;
const int& ref = 65;
В наведеному прикладі:
rA
і rB
посилання на тип int
;
foo()
це функція, яка як вихідне значення повертає посилання на тип int
;
bar()
є функцією, яка приймає параметр посилання, який посилається на тип int
;
MyClass
є класом з членом, який посилається на int
;
funcX()
це функція яка повертає тип int
, який не є посиланням.
xFunc()
є аліасом функції funcX()
;
const int& ref
є константним посиланням, яке вказує на ділянку пам'яті, в якій зберігається значення 65.
Порівняння з вказівниками
C++ посилання мають наступні відмінності в порівнянні з вказівниками:
- На відміну від вказівника, посилання повинно бути ініціалізоване не адресою об'єкту, а його значенням.
- Не можливо звернутися на пряму до об'єкту посилання, після того як він був визначений; будь-яке входження його імені звертається безпосередньо до об'єкту, на який воно посилається.
- Після того як посилання було створене, посилання не можливо змінити так, щоб воно почало вказувати на інший об'єкт; Воно не може бути змінено. З вказівниками це можливо зробити.
- Посилання не може бути пустим, тобто приймати значення null, в той час як вказівники можуть; Кожне посилання звертається до якогось об'єкту, хоча він може не бути дійсним. З цієї причини, контейнери з посиланнями не дозволені.
- Посилання повинно бути ініціалізованим. Оскільки не можливо змінити посилання, воно повинно бути ініціалізоване, як тільки воно було об’явлене. Зокрема локальні і глобальні змінні потрібно ініціалізувати одразу при їх об’явленні, а посилання, які є полями класу мають бути ініціалізовані в списку ініціалізації конструктора класу. Наприклад:
int& k; // Помилка: k об’явлено як посилання, але не є визначеним
Існує спосіб приведення між собою посилань і вказівників: оператор взяття адреси (&
) поверне вказівник, який звертається на той самий об'єкт якщо його застосувати до посилання, а посилання яке ініціалізоване за допомогою операції розіменування вказівника (*
) буде посилатися на той самий об'єкт, що і вказівник, де це можливо без створення непередбачуваної поведінки. Ця схожість полягає в типовій реалізації компілятора, яка призводить до того, що посилання успішно перетворюються у вказівники, які автоматично розіменовуються при кожному використанні. Хоча це загальна практика, стандарт С++ не змушує реалізовувати посилання у вигляді вказівників.
Наслідком цього є те, що в багатьох ситуаціях, оперування змінною з автоматичним чи статичним періодом життя за допомогою посилання, дуже схоже синтаксично на прямий доступ до неї, але може містити неявні операції розіменування, які є більш затратними.
Оскільки операції з посиланнями є обмеженими, вони набагато легші в розумінні в порівнянні з вказівниками, тому призводять до меншої кількості помилок. Існує багато ситуацій, після яких вказівники можуть стати не дійсними, починаючи від можливості прийняти нульове значення до виходу за межі виділеної пам'яті, в той час як посилання можуть стати не дійсними в двох випадках:
- посилання звертається до об'єкта з автоматичною алокацією, який вийшов за межі блоку видимості,
- посилання звертається на блок пам'яті, яка була виділена динамічно і була звільнена.
Це легко контролювати автоматично, якщо посилання є статичним, але це є проблемою, якщо посилання є членом динамічно визначеного об'єкту, в такому випадку дуже складно бути впевненим в дійсності посилання. Це є єдиними складностями при роботі з посиланнями.
Аналоги
- Посилання можна розглядати як «символічне посилання» в термінології файлових систем. З символічними посиланнями можна працювати так ніби вони безпосередньо є тим файлом, на який звертаються, але їх можна видаляти так, що файл залишається на місці. Так само і в програмуванні, посилання можуть знищуватися (коли вони виходять за межі видимості, або видаляються явно у випадку коли вони були визначені в купі), а початковий об'єкт, на який посилалися, залишається. Так само, з того часу як символічне посилання було створене, його не можна змінити, щоб воно почало вказувати в інше місце.[1]
Використання посилань
Дуже зручною заміною вказівникам, посилання стають при використанні їх як параметрів функції, де вони використовуються для передачі вихідних параметрів (які можуть змінюватися функцією) без будь-яких явних процедур взяття адреси при викликанні функції. Наприклад:
void square(int x, int& result)
{
result = x * x;
}
Наступний виклик, поверне значення 9 у змінну y:
square(3, y);
В той час як наступний виклик, призведе до помилки компіляції, оскільки параметр у вигляді посилання не визначений як константний з ключовим словом const може використовуватися лише до адресних значень:
square(3, 6);
Якщо функція повертає посилання, це дозволяє присвоювати до неї значення:
int& preinc(int& x)
{
return ++x; // "повернення x++;" призвело б до не вірного результату
}
preinc(y) = 5; // те саме що ++y, y = 5
В багатьох реалізаціях, звичайний механізм передачі параметрів зазвичай потребує затратної операції копіювання, у разі якщо їх багато. Посилання із модифікатором const є корисним методом передачі великих об’єктів у функції, і усуває ймовірність переповнення:
void f_slow(BigObject x) { /* ... */ }
void f_fast(const BigObject& x) { /* ... */ }
BigObject y;
f_slow(y); // повільно, копіює значення y у параметр x
f_fast(y); // швидко, дає прямий доступ, без дозволу виконувати зміни, до y
Якщо функція f_fast()
все ж таки потребує створення власної копії об’єкту x, яку вона зможе змінювати, вона має створити копію явним способом. Хоча таку саму техніку можна реалізувати із застосуванням вказівників, це буде вимагати змінити кожен виклик цієї функції і додати оператор взяття адреси до аргументу (&
).
Поліморфна поведінка
Продовжуючи порівняння вказівників і посилань (в контексті C++), вони мають схожі можливості при застосуванні до поліморфних об'єктів:
#include <iostream>
using namespace std;
class A
{
public:
A() {}
virtual void print() { cout << "Це клас A\n"; }
};
class B: public A
{
public:
B() {}
virtual void print() { cout << "Це клас B\n"; }
};
int main()
{
A a;
A& refToa = a;
B b;
A& refTob = b;
refToa.print();
refTob.print();
return 0;
}
Приведений код C++ видає наступний вивід на екран:
Це клас A
Це клас B