Ссылка (C++)
В языке программирования C++ ссылка — это простой ссылочный тип, менее мощный, но более безопасный, чем указатель, унаследованый от языка Си. Название C++ ссылка может приводить к путанице, так как в информатике под ссылкой понимается обобщенный концептуальный тип, а указатели и С++ ссылки являются специфическими реализациями ссылочного типа.
Содержание |
Введение [править]
Если мы объявили переменную без спецификатора extern, то для хранения значений выделяется память. Чтобы изменить или прочитать значение переменной (то есть значение находящейся в этой области памяти), мы обращаемся по имени этой переменной. В языке C имя сущности (переменной, типа, функции и т.д.) — это идентификатор. С точки зрения программиста, объявляя ссылку (или же указывая, что она будет возвращаемым значением или аргументом функции), мы задаём альтернативный идентификатор для уже созданного объекта. В языке C ссылок нет. С точки зрения реализации, ссылка — это, по сути, указатель, который жестко привязан к области памяти, на которую он указывает, и который автоматически разыменовывается, когда мы обращаемся по имени ссылки (это легко проверить, дизассемблируя простой пример).
Например:
/* конкретные адреса переменных могут быть другими */
int a; //переменная с именем "a" типа int размещена по адресу 0xbfd86d6c
int &ra = a; //задано альтернативное имя (ra) для переменной по адресу 0xbfd86d6c
/* символ "&" используемый для уже созданного объекта является операцией взятия адреса *
* (и эта операция не есть ссылка), то есть &a тут означает получить адрес переменной *
* к которому привязано имя "a" */
cout << &a << '\n' << &ra << '\n';
В stdout будет записано:
0xbfd86d6c
0xbfd86d6c
То есть оба имени "a" и "ra" привязаны к одному и тому же адресу.
Ссылки нельзя объявлять без привязки к переменной (то есть не инициализировав при объявлении). После объявления ссылки её невозможно привязать к другой переменной.
Важно отличать ссылки от оператора взятия адреса & (address of). Оператор взятия адреса используется для уже созданного объекта с целью получить его адрес (то есть адрес области памяти, где хранятся значения), а ссылка это только задание альтернативного имени объекта (с точки зрения программиста, а не реализации). Например:
int a; //переменная типа int размещена по адресу 0xbfd86d6c с именем "a"
int b = 3;
/* создан указатель с именем "p" по адресу 0xbf971c4c, значение этого указателя *
* адрес объекта с именем "a" - 0xbfd86d6c (это значение можно будет менять) */
int *p = &a;
p = &b; //присваиваем указателю новое значение соответствующее адресу переменной "b"
Отличие указателя от ссылки в том, что получить само значение переменной, на который указывает указатель, можно только выполнив операцию разыменовывания * (символ "*" в объявлении является объявлением указателя, а при применении к уже созданной переменной является оператором разыменовывания). Например:
int a = 3;
int *p = &a; //объявили, создали, и инициализировали объект
// здесь к уже созданному объекту с именем "p" применяется оператор "*", который означает
// считать значение из "p", которое является адресом и далее считать данные по этому адресу
cout << *p << '\n';
В stdout будет записано:
3
Синтаксис и терминология [править]
Объявление вида:
<Type> & <Name>
где <Type> — тип и <Name> — идентификатор, указывает идентификатор, чьим типом является ссылка на <Type>.
Примеры:
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;
Здесь, rA и rB являются типами «ссылок на int», foo() — функция, возвращающая ссылку на int, bar() — функция с ссылкой в качестве параметра, которая ссылается на int, MyClass — класс (class) с членом, ссылающимся на int, funcX() — функция, возвращающая int, xFunc() — псевдоним для funcX.
Типы, относящиеся к «ссылка на <Type>», иногда называются ссылочными типами. Идентификаторы ссылочного типа называются ссылочными переменными. Называть их переменными в строгом смысле будет неправильно (показано дальше).
Связь с указателями [править]
C++ ссылки отличаются от указателей несколькими особенностями:
- Невозможно ссылаться напрямую на объект ссылочного типа после его определения; каждое упоминание его имени напрямую представляет объект, на который он ссылается.
- В качестве результата первого указания не могут быть выполнены никакие арифметические вычисления, приведения типов, взятия адреса и т.п.
- После создания ссылки ее нельзя перевести на другой объект; в таких случаях говорят, не может быть переопределена. Это часто делают с указателями.
- Ссылки не могут быть null (т.е.указывать в никуда), тогда как указатели - могут; каждая ссылка ссылается на некий объект, вне зависимости от его корректности.
- Ссылки не могут быть неинициализированными. Так как невозможно переинициализировать ссылку, она должна быть инициализирована сразу после создания. В частности, локальные и глобальные переменные должны быть проинициализированы там же, где они определены, а ссылки, которые являются данными-членами сущностей класса, должны быть инициализированы в списке инициализатора конструктора класса.
Пример:
- int& k; // компилятор выдаст сообщение: ошибка: 'k' declared as reference but not initialized ('k' объявлена как ссылка, но не инициализирована)
Существует простое преобразование между указателями и ссылками: операция взятия адреса (&) получает указатель, ссылающийся на тот же самый объект при переходе по ссылке, а ссылка, которая инициализирована при разыменовании (*) указателя будет указывать на тот же объект, что и указатель, где это возможно без неопределенного поведения. Эта тождественность - отражение типичной реализации, которая весьма эффективно превращает ссылки в указатели, которые неявно разыменовываются при каждом использовании.
Как следствие - во многих реализациях работа с переменными с автоматическим или статическим жизненным циклом по ссылке, хоть синтаксически и похожа на прямой доступ, может вызвать скрытые разыменования с дополнительными расходами.
Кроме того, из-за ограничения операций над ссылками, они намного легче в понимании, чем указатели, а также более защищены от ошибок. Тогда как указатели могут стать некорректными благодаря множеству причин, начиная с указания на null-значения и выходов за границы и до использования недопустимых приведений типов, ссылка может стать некорректной лишь в двух случаях:
- Если она ссылается на объект с автоматическим размещением в памяти, с завершившимся временем жизни,
- Если она ссылается на объект, находящийся в блоке динамической памяти, который был освобожден.
Первый вариант легко обнаруживается автоматически если ссылка имеет статическое размещение, но возникают проблемы, если ссылка - член динамически размещенного объекта; от второго защищаться сложнее. Это единственный недостаток ссылок, который может быть нивелирован при разумной политике выделения памяти.
Применение ссылок [править]
- Помимо удобной замены указателям, еще одним полезным применением ссылок являются списки параметров функции, при помощи которых они могут передавать параметры, используемые для вывода без явного взятия адреса вызывающим. Например:
void square(int x, int& result) { result = x * x; }
Тогда следующий вызов поместит 9 в y:
square(3, y);
Тем не менее, следующий вызов приведет к ошибке компиляции, так как только параметры ссылки, не помеченные const, могут быть адресуемыми значениями:
square(3, 6);
- Возврат ссылки также позволяет непредусмотренный синтаксис, в котором вызовы функции могут быть присвоены:
int& preinc(int& x) { ++x; return 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 для модификации, то она должна создать копию явным образом. Хотя того же эффекта можно достичь с использованием указателей, это потребует модификации каждого вызова функции, добавив к аргументу неуклюжий оператор взятия по адресу (&), причем отменить изменения будет не менее сложно, если в дальнейшем объект станет маленьким.
- Причиной введения ссылок в язык С++ в основном являлась необходимость перегрузки операторов, применяемых к объектам пользовательских типов (классов). Как упоминалось выше, передача по значению громоздких объектов в качестве операндов вызывала бы лишние затраты на их копирование. С другой стороны, передача операндов по адресу с использованием указателей, приводит к необходимости взятия адресов операндов в выражениях. Например:
class BigClass { //... friend BigClass operator-(BigClass* left, BigClass* right); //... }; BigClass x, y, z; //... x = &y - &z;
Однако, выражение &y - &z уже имело определённый смысл в языке С.
Цитаты [править]
Ссылки определены стандартом ISO C++ следующим образом (исключая раздел примеров):
В объявлении T D, где D имеет вид
& D1
а типом идентификатора в объявлении T D1 является производный тип ("derived-declarator-type-list) T,” тогда типом идентификатора D будет производная (“derived-declarator-type-list) ссылка на T.” Cv-ссылки являются плохо согласованными, исключая ситуацию, когда cv-квалификаторы (от англ. const ("константный") и volatile ("временный")) представлены через использование typedef (7.1.3) или шаблон аргумента типа (14.3), в случае чего игнорируются cv-квалификаторы. [Пример: в коде
typedef int& A; const A aref = 3; // плохо согласовано; // неконстантная ссылка инициализируется rvalue
тип aref является “ссылкой на int”, а не “const ссылается на int”. ] [Примечание: ссылка может восприниматься как имя объекта. ] Объявление, указывающее тип “ссылается на cv void”, некорректно.
Это не определяет, требуется ли ссылке выделение памяти (3.7).
Не должно быть ссылок на ссылки, массивов ссылок, а также указателей на ссылки. Объявление ссылки должно содержать инициализатор (8.5.3), за исключением случая, когда объявление содержит явный указатель extern (7.1.1) - объявление члена класса (9.2) внутри объявления класса, объявление параметра или возврат типа (8.3.5); смотри 3.1. Ссылка должна при инициализации ссылаться на корректный объект или функцию. [Примечание: точнее говоря, ссылка на null не может существовать в корректно написанной программе, так как единственным способом создать подобную ссылку является связывание ее с "объектом", полученном при помощи разыменования нуль-указателя, что вызывает неопределенное поведение. Как описано в 9.6, ссылка не может ограничиваться напрямую битовым полем. ] | cite = ISO/IEC 14882:1998(E), стандарт ISO C++, раздел 8.3.2 [dcl.ref]
См. также [править]
Дополнительные источники [править]
- Ссылки в кратком FAQ по C++ (англ.)
- Простые C++ Указатели и Ссылки (англ.)

