Ссылка (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>.

Примеры:

  1. int A = 5;
  2. int& rA = A;
  3. extern int& rB;
  4. int& foo ();
  5. void bar (int& rP);
  6. class MyClass { int& m_b; /* ... */ };
  7. 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]

См. также[править | править исходный текст]

Ссылки в программировании

Дополнительные источники[править | править исходный текст]