Способ передачи параметра определяет, работает функция с копией значения или с исходным объектом. В C++ три основных способа: по значению, через указатель и по ссылке.

Зачем нужно

Некоторым функциям достаточно значения (Min), а некоторые существуют ради изменения переданных объектов (Swap). Кроме того, копировать большой объект при каждом вызове дорого. Тип параметра — единственное место, где это видно: одной строки вызова недостаточно, нужно смотреть на объявление.

Как работает

Сквозной пример — обмен двух float:

По значению

void DummySwap(float lhs, float rhs) {
  float tmp = lhs;
  lhs = rhs;
  rhs = tmp;
}
 
DummySwap(z, v);  // z и v не изменились

Параметры — отдельные переменные, инициализированные копиями аргументов. Обмен проходит между копиями. Присваивание параметру по значению не является присваиванием аргументу.

Через указатель

void SwapByPointer(float* lhs, float* rhs) {
  float tmp = *lhs;
  *lhs = *rhs;
  *rhs = tmp;
}
 
SwapByPointer(&z, &v);  // 2 1

Указатель тоже передаётся по значению — копируется адрес. Но через копию адреса можно добраться до того же объекта:

void SetZero(float* ptr) {
  *ptr = 0.0f;    // меняет объект снаружи
  ptr = nullptr;  // меняет только локальную копию
}

Минусы: в вызове нужно &, в теле — *, а nullptr функция сама не отличит от годного адреса.

По ссылке

void Swap(float& lhs, float& rhs) {
  float tmp = lhs;
  lhs = rhs;
  rhs = tmp;
}
 
Swap(z, v);  // 2 1

Параметры — ссылки, на время вызова привязанные к z и v. Тело буквально такое же, как у DummySwap, но меняет исходные объекты. Литерал передать нельзя: Swap(1.0f, 2.0f) — CE.

По константной ссылке

void PrintX(const int& x) {
  std::cout << "x=" << x << '\n';
}
 
PrintX(x);   // привязка к переменной
PrintX(10);  // привязка к временному объекту

Копии нет, а запись через параметр запрещена компилятором. Принимает и переменные, и временные значения.

Что выбирать

НужноСпособ
только значение, объект маленькийпо значению
изменить объект вызывающего кодапо ссылке (T&)
только читать большой объектпо константной ссылке (const T&)
«объекта может не быть»указатель (может быть nullptr)

Правило с лекции: по ссылке стоит передавать любой объект больше двух машинных слов (16 байт на большинстве систем). int по константной ссылке передавать незачем — это делалось в лекции, чтобы разобрать механизм.

Не из лекции

Последняя строка таблицы — обобщение, а не формулировка лекции. Лекция только замечает, что параметр-указатель не проверяет адрес, а у ссылки состояния «ни с чем не связана» нет.

Тонкости

  • const на параметре по значению (const int y, float* const p) запрещает менять локальную копию и не меняет сигнатуру. const float* вместо float* — меняет: это уже про доступ к объекту.
  • Массив по значению не передаётся: в функцию уходит указатель на нулевой элемент, размер передают отдельно или вторым указателем на конец, см. Массив.
  • Возврат значения тоже не меняет аргументы: чтобы записать результат в a, нужно a = Max(a, b);.

Частые ошибки

  • Ждать, что изменение параметра по значению видно снаружи.
  • В функции с параметрами-указателями написать lhs = rhs вместо *lhs = *rhs.
  • Передавать nullptr в функцию, которая разыменовывает параметр.
  • Передавать литерал в параметр T&.
  • Копировать большой объект по значению, когда достаточно const T&.