Передача параметров в C++: значение, ссылка, указатель
Копия структуры на каждый вызов съедает время, а лишняя ссылка ломает безопасность. Разбираем, когда брать по значению, когда по ссылке, а когда указатель — с ценой каждого решения.

Функция process(std::vector<int> data) копирует весь вектор при каждом вызове. Если внутри цикла на миллион итераций она получает контейнер на 10 000 элементов — это 10 миллиардов копий int, которые вообще не нужны. Замена сигнатуры на const std::vector<int>& убирает копирование целиком. Способ передачи параметра в C++ — это не стилистика, а решение о том, кто владеет данными, кто платит за копию и может ли функция сломать ваш объект. Разбираем три механизма и цену каждого. Примеры компилируются под C++17 и новее.
Три способа и что происходит с данными
У передачи параметра есть три базовых формы, и различаются они не синтаксисом, а тем, что попадает в тело функции.
- По значению (
T x) — в функцию попадает копия. Изменения внутри не видны снаружи. Платите за конструктор копирования. - По ссылке (
T& x) — функция работает с тем же объектом, что и вызывающий код. Копии нет. Изменения видны снаружи. Ссылка не может быть nullptr и не переназначается. - По указателю (
T* x) — в функцию попадает адрес объекта (копируется сам указатель, 8 байт на 64-битной системе). Через разыменование меняете исходный объект. Указатель может быть nullptr, и это надо проверять.
Ключевое различие ссылки и указателя — не в производительности (обе передают адрес), а в контракте. Ссылка обещает: объект существует. Указатель допускает: объекта может не быть.
По значению: когда копия оправдана
Копирование дешёвых типов — норма. int, double, указатель, небольшой POD-объект помещаются в регистр или пару регистров, и передача копии не медленнее передачи адреса, а иногда быстрее: компилятору не нужно разыменовывать ссылку.
// Дёшево и правильно: копия int ничего не стоит
int square(int value) {
return value * value;
}
Ориентир: если sizeof(T) не больше размера двух указателей (16 байт на типичной 64-битной платформе) и тип тривиально копируемый, берите по значению. Для std::string, std::vector и прочих контейнеров с динамической памятью копия по значению означает выделение кучи и копирование содержимого — дорого.
По значению ради перемещения
Есть один неочевидный случай, где брать по значению правильно даже для тяжёлого типа: если функция всё равно сохраняет параметр себе. Тогда параметр по значению плюс std::move внутри позволяет вызывающему коду передать временный объект без копии — сработает конструктор перемещения.
#include <string>
#include <utility>
class User {
std::string name_;
public:
// Принимаем по значению, затем перемещаем в поле
explicit User(std::string name) : name_(std::move(name)) {}
};
// User u("Anna"); // временная строка переместится, копии не будет
Это идиома «принять по значению и переместить». Если функция только читает параметр и никуда не сохраняет — она не нужна, берите const&.
По ссылке: чтение без копии и запись через параметр
Ссылка решает две разные задачи, и путать их нельзя.
const-ссылка для чтения
Самый частый и самый безопасный способ передать тяжёлый объект, который функция только читает. Копии нет, а const гарантирует, что функция объект не испортит.
#include <vector>
#include <numeric>
// Читаем большой вектор без копирования
long long sum(const std::vector<int>& data) {
return std::accumulate(data.begin(), data.end(), 0LL);
}
Здесь есть подводный камень с временными объектами и висячими ссылками. const& продлевает жизнь временного объекта только на время вызова — это безопасно. Но если функция вернёт ссылку на свой параметр, а вызвана она была с временным объектом, вы получите висячую ссылку.
Неконстантная ссылка для изменения
Когда функция должна изменить переданный объект, неконстантная ссылка делает это явно в реализации, но невидимо в месте вызова.
#include <string>
// Дописываем в строку на месте
void append_suffix(std::string& text, const std::string& suffix) {
text += suffix;
}
// std::string log = "error";
// append_suffix(log, ": disk full"); // log изменился, но по коду вызова это не видно
Именно эта невидимость — цена. В строке append_suffix(log, ...) ничто не подсказывает, что log будет изменён. Читателю кода придётся лезть в сигнатуру. Поэтому многие команды в стайлгайде запрещают выходные параметры через неконстантную ссылку и требуют возвращать результат или использовать указатель — чтобы в месте вызова стоял &log как маркер «сюда пишут».
Ссылка говорит: объект точно есть, и я его либо читаю, либо меняю. Указатель говорит: объекта может не быть — проверь, прежде чем трогать. Выбор между ними — это выбор контракта, а не микрооптимизация.
По указателю: когда объекта может не быть
Указатель как параметр оправдан в двух ситуациях: параметр опциональный (может отсутствовать) и вы явно передаёте владение или адрес для записи, который хотите видеть в месте вызова.
#include <string>
// Опциональный выходной параметр: если nullptr — счётчик не нужен
bool parse_line(const std::string& line, int* out_count) {
if (line.empty()) {
return false;
}
if (out_count != nullptr) { // проверка обязательна
*out_count = static_cast<int>(line.size());
}
return true;
}
Главная опасность — разыменование nullptr. Ссылку разыменовать нельзя случайно, потому что она обязана ссылаться на объект. Указатель — можно, и это неопределённое поведение с падением в лучшем случае. Каждое разыменование параметра-указателя, который допускает nullptr, должно быть под проверкой.
Для необязательного значения, которое не про запись наружу, в C++17 есть std::optional — он выражает «значение может отсутствовать» без сырого указателя и без вопроса о владении памятью. Владение динамическими объектами — отдельная тема, там нужны std::unique_ptr и std::shared_ptr, а не сырой указатель в сигнатуре; здесь мы их не разбираем.
Таблица: что выбрать
| Ситуация | Способ | Цена |
|---|---|---|
| Дешёвый тип (int, double, указатель), только чтение | T по значению | Копия почти бесплатна |
| Тяжёлый тип, только чтение | const T& | Ноль копий, нельзя изменить |
| Функция сохраняет параметр себе | T по значению + std::move | Одно перемещение вместо копии |
| Нужно изменить объект вызывающего | T& или T* | Изменение не видно (ссылка) / видно по & (указатель) |
| Параметр опциональный (может не быть) | T* или std::optional<T> | Обязательная проверка на nullptr / чуть больше кода |
Типичные ошибки
- Передача тяжёлого объекта по значению без надобности.
void log(std::string msg)при вызове с существующей переменной копирует всю строку. Если строка только читается — нуженconst std::string&. - Возврат ссылки на параметр, переданный по значению. Параметр живёт до конца функции — ссылка на него станет висячей сразу после возврата.
- Разыменование указателя без проверки на nullptr. Если сигнатура допускает
nullptr, каждая работа через*ptrдолжна быть подif (ptr). - Неконстантная ссылка там, где хватило бы const. Забыли
const— и функция теперь может изменить объект, а компилятор не помешает случайной записи.
Что делать дальше
Пройдите по сигнатурам в своём коде и проверьте одно: у каждого параметра тяжёлого типа, который функция только читает, стоит const&. Это самая частая и самая дешёвая победа по производительности. Дальше — включите предупреждения компилятора: -Wall -Wextra в GCC и Clang ловят часть ошибок с параметрами, а статические анализаторы вроде clang-tidy подсказывают, где параметр стоит взять по const& вместо значения. Отдельно изучите передачу владения через std::unique_ptr и std::shared_ptr — это следующий уровень, где вопрос уже не «копия или ссылка», а «кто освобождает память».
Создатель презентаций: Структура и текст каждого слайда: заголовки-утверждения, одна мысль на слайд, заметки докладчику.
Спросить за 150 ₽Источники: C++ Core Guidelines: F.15–F.21 (передача параметров), cppreference: std::move
Частые вопросы
А если у меня старый стандарт, C++11 или C++14?
Что быстрее — ссылка или указатель?
Всегда ли const-ссылка лучше передачи по значению?
Почему нельзя вернуть ссылку на параметр, переданный по значению?
Не сработало: функция меняет объект, но снаружи изменения не видны. В чём дело?
Стоит ли использовать выходные параметры через неконстантную ссылку в проде?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.