Menu

Ссылки в C++: передача по ссылке, const& и псевдонимы

Ссылки в C++ простыми словами: как & в параметре создаёт псевдоним, почему передача по ссылке избегает копий и позволяет функции изменять переменные вызывающего кода, и когда выбирать const& и ссылки вместо возврата по значению.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Другое имя для той же сущности

На странице о параметрах функций каждый аргумент копировался в функцию. Именно из-за этой копии функция не может изменить переменную вызывающего кода — она всегда видит лишь свой дубликат. Ссылка разрушает эту стену. Это псевдоним: второе имя, привязанное к существующей переменной и разделяющее ровно ту же память.

Ссылку создают с помощью & в объявлении. После привязки ссылка и оригинал неразличимы:

Два правила делают ссылки безопасными и предсказуемыми: ссылка должна быть инициализирована в момент объявления (int& r; — ошибка компиляции), и её нельзя переназначить на что-то другое впоследствии. Присваивание ссылке всегда пишет в то, к чему она была изначально привязана.

Передача по ссылке: позвольте функции дотянуться обратно

Настоящая выгода — в функциях. Поставьте & у параметра, и функция получит псевдоним аргумента вызывающего кода вместо копии. Теперь изменения внутри функции видны и снаружи:

Уберите &, и addBonus увеличил бы одноразовую копию, оставив total равным 100. Этот единственный символ и есть вся разница. Это канонический способ написать функцию, которая возвращает более одного результата или изменяет свои входные данные на месте. Классический пример — обмен двух переменных:

Без ссылок swapValues обменял бы только локальные копии, и x/y остались бы 1 2. (В стандартной библиотеке уже есть std::swap, но написать его самому полезно: это наглядно показывает, что даёт параметр-ссылка.)

Ссылки на const: читай быстро, обещай не трогать

Передача по ссылке также избегает копирования — а для большого объекта эта копия может быть дорогой. Но простой параметр T& сигнализирует «я могу это изменить», что вводит в заблуждение, когда вы хотите только читать. Решение — const T&: вы получаете скорость без копирования, как у ссылки, и обещание, гарантированное компилятором, что функция не изменит аргумент.

Не-const-ссылка может привязаться только к изменяемой переменной, но const-ссылка может также привязаться к литералам и временным объектам — вот почему greet("literal works too") компилируется. Практическое правило выбора типа параметра:

void f(int x)              // дешёвый тип, только чтение -> просто копируй
void f(const string& s)   // тяжёлый тип, только чтение -> const-ссылка
void f(string& s)         // ты намерен изменить объект вызывающего кода

По умолчанию используйте const T& для любого классового типа, который вы только читаете (string, vector, ваши собственные структуры), и оставляйте не-const-ссылку для случаев, когда вы действительно хотите записать обратно.

Возврат ссылки

Функция также может возвращать ссылку, передавая вызывающему коду псевдоним на нечто уже существующее. Это распространено в коде контейнеров — именно это позволяет работать v[i] = 5 и именно это делает operator[] под капотом:

Поскольку at возвращает int&, выражение вызова at(data, 1) само является lvalue, которому можно присваивать. Верните вместо этого простой int, и at(data, 1) = 42 не скомпилируется — вы бы присваивали временной копии.

Главная ловушка: висячие ссылки

Ссылка ничем не владеет; она лишь указывает на память, которая живёт где-то ещё. Если эта память умирает, пока ссылка ещё используется, у вас висячая ссылка, и чтение через неё — неопределённое поведение: может вывести мусор, упасть или казаться работающим, пока не испортит вам день в продакшене. Классическая ошибка — вернуть ссылку на локальную переменную:

int& broken() {
    int local = 42;
    return local;   // BUG: local уничтожается, когда broken() возвращает управление
}                   // возвращённая ссылка повисает

int main() {
    int& r = broken();
    cout << r << "\n";   // НЕОПРЕДЕЛЁННОЕ ПОВЕДЕНИЕ - читает мёртвую память
}

Переменная local исчезает в тот момент, когда broken возвращает управление, поэтому ссылка указывает на уже освобождённое пространство стека. Возвращайте ссылку только на то, что переживёт вызов — параметр, переданный по ссылке, элемент данных или static. Если значение вычисляется внутри функции, возвращайте по значению и позвольте компилятору оптимизировать копию. Та же ловушка подстерегает циклы на основе диапазона и любую ссылку, привязанную к временному объекту: никогда не удерживайте ссылку дольше времени жизни того, что она именует.

Далее: перегрузка функций

Ссылки дают вам второй регулятор у каждого параметра — копия или псевдоним, изменяемый или const — и этот регулятор напрямую связан со следующей темой. Далее перегрузка функций позволяет определить несколько функций с одним именем, но разными списками параметров, и компилятор выбирает нужную, сопоставляя типы аргументов — в том числе передаются ли они по значению, по ссылке или по const-ссылке.

Часто задаваемые вопросы

Что такое ссылка в C++?

Ссылка — это псевдоним существующей переменной, другое имя для той же памяти. Её создают с помощью & в объявлении: int& r = x;. После этого r и x взаимозаменяемы; изменение одного меняет другое. Ссылки должны быть инициализированы при объявлении и никогда не могут быть переназначены на другую переменную.

В чём разница между передачей по значению и передачей по ссылке в C++?

Передача по значению (void f(int x)) копирует аргумент, поэтому функция работает со своей копией, а переменная вызывающего кода остаётся нетронутой. Передача по ссылке (void f(int& x)) даёт функции прямой доступ к переменной вызывающего кода, поэтому изменения видны после вызова — и копия не создаётся, что важно для крупных объектов.

Когда следует использовать параметры-ссылки на const в C++?

Используйте const T&, когда функции нужно только читать параметр, но тип дорого копировать (string, vector, большие структуры). Вы получаете скорость без копирования, как у ссылки, плюс гарантию компилятора, что функция не изменит значение вызывающего кода. Для дешёвых типов вроде int или double обычная передача по значению проще и так же быстра.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ