Программа курса← Программирование на языке C++
1Введение в язык C++
2Как выполняются программы на C++
3Структуры и классы
4Объектно-ориентированное программирование
5Дополнительные возможности C++
6Шаблоны
7Методические материалы

Ссылки: псевдоним вместо адреса

Указатель решает две разные задачи: «объект где-то там, в куче» и «дай функции доступ к моей переменной». Для второй он избыточен – адрес приходится брать, потом разыменовывать, и всюду возможен nullptr. Ссылка закрывает ровно эту задачу и делает это без синтаксического шума.

Псевдоним

int value = 42;
int& ref = value;     // ref – другое имя для value

ref = 100;            // value == 100
std::cout << value;   // 100
std::cout << &ref;    // тот же адрес, что и &value

ref – не копия и не отдельный объект. Это второе имя для той же ячейки памяти.

Три правила, из которых следует всё остальное:

  1. Ссылку обязательно инициализировать при объявлении. int& r; не скомпилируется.
  2. Привязку нельзя изменить. ref = other присваивает значение other в value, а не перенаправляет ссылку.
  3. Ссылка не может быть пустой. Проверять её на nullptr бессмысленно – такого состояния нет.

Занимает ли ссылка память? Стандарт не обязывает. На практике компилятор либо полностью её устраняет (просто работает с исходной переменной), либо реализует как указатель – например, когда ссылка является параметром функции, которую не удалось встроить. Считать её «указателем с приятным синтаксисом» – рабочая модель, но помнить про пункт 3 всё равно нужно.

Ссылка против указателя

Указатель Ссылка
Может быть пустым да, nullptr нет
Можно переприсвоить да нет
Синтаксис доступа *p, p->field как у обычной переменной
Арифметика да нет
Когда брать объект может отсутствовать; нужно перемещаться по памяти объект точно есть и один

Практическое правило: ссылка по умолчанию, указатель – когда нужна пустота или переприсваивание. Функция, принимающая Widget&, самой сигнатурой говорит «объект обязателен»; принимающая Widget* – «может быть и nullptr, я проверю».

Ссылки в параметрах

Это главное применение. Три варианта и когда какой:

void ByValue(std::string s);          // копия: функция получает свой экземпляр
void ByRef(std::string& s);           // без копии, функция может менять
void ByConstRef(const std::string& s);// без копии, менять нельзя
std::string text = "hello";

ByValue(text);       // скопирована вся строка
ByRef(text);         // text может измениться
ByConstRef(text);    // text не изменится, копии нет

Для int, double, указателя копия дешевле ссылки – их передают по значению. Для строки, вектора, любого объекта с полями – const T&.

Читая чужой код, обращайте внимание на & без const в сигнатуре: это единственный внешний признак того, что функция изменит ваш объект. У вызова Update(config) по виду не отличить, передаётся копия или оригинал, – смотреть надо в объявление.

Константная ссылка и временные объекты

У const T& есть особенность: к ней можно привязать временный объект, и время его жизни продлевается до конца жизни ссылки.

void Print(const std::string& s);

Print("literal");            // создан временный std::string, привязан, уничтожен после вызова

const std::string& r = std::string("temp");   // временный живёт, пока жива r

С неконстантной ссылкой так нельзя – std::string& r = std::string("temp"); не скомпилируется. Логика простая: изменять то, что вот-вот исчезнет, почти наверняка ошибка.

Продление жизни работает только для прямой привязки. Если функция вернула ссылку на временный объект, продлевать уже нечего:
const std::string& bad = MakeName().substr(0, 3); – временный уничтожается в конце выражения, bad становится висячей. Ссылка так же легко превращается в мусор, как и указатель, просто выглядит безобиднее.

Ссылка на массив

Обычная передача массива теряет его размер. Ссылка – нет:

void Foo(int (&a)[3]) { /* размер известен и проверяется */ }

int good[3] = {};
int bad[1] = {};

Foo(good);   // ок
Foo(bad);    // ошибка компиляции: invalid initialization of reference of type 'int (&)[3]'

Скобки вокруг &a обязательны: без них int& a[3] читалось бы как массив ссылок, которого в C++ не бывает.

Конструкция выглядит громоздко и в чистом виде используется редко. Но в связке с шаблонами она даёт способ узнать размер массива на этапе компиляции – к этому мы вернёмся в модуле про шаблоны.

Возврат по ссылке

Функция может вернуть ссылку – если возвращаемый объект переживёт вызов.

class Buffer {
public:
    int& At(size_t i) { return data_[i]; }     // объект живёт в *this, всё честно

private:
    int data_[128];
};

Buffer b;
b.At(3) = 42;        // работает: слева от = стоит ссылка на элемент

И чего делать нельзя:

int& Broken() {
    int x = 42;
    return x;        // кадр умрёт на выходе, ссылка станет висячей
}

Правило то же, что и для указателей: не возвращайте ссылку на то, что живёт в кадре функции. Возвращать по значению безопасно и почти всегда бесплатно – компилятор строит результат прямо там, где он нужен вызывающему.