10.17 – Ссылки и const
Ссылка на константное значение
Так же, как можно объявить указатель на константное значение, можно объявить и ссылку на константное значение. Это делается путем объявления ссылки с помощью ключевого слова const
.
const int value{ 5 };
const int &ref{ value }; // ref - ссылка на константное значение
Ссылку на константное значение часто для краткости называют константной ссылкой, хотя на самом деле это приводит к некоторым противоречиям с названиями указателей.
Инициализация ссылок на константные значения
В отличие от ссылок на неконстантные значения, которые могут быть инициализированы только неконстантными l-значениями, ссылки на константные значения могут быть инициализированы неконстантными l-значениями, константными l-значениями и r-значениями.
int x{ 5 };
const int& ref1{ x }; // ok, x - неконстантное l-значение
const int y{ 7 };
const int& ref2{ y }; // ok, y - константное l-значение
const int& ref3{ 6 }; // ok, 6 - r-значение
Как и указатель на константное значение, ссылка на константное значение может ссылаться на неконстантную переменную. При доступе через ссылку на константное значение переменная считается константной, даже если исходная переменная таковой не является:
int value{ 5 };
const int& ref{ value }; // создаем константную ссылку на переменную value
value = 6; // ok, value не является константной
ref = 7; // недопустимо - ref является константной
Ссылки на r-значения продлевают время жизни значения, на которое ссылаются
Обычно r-значения имеют область видимости выражения, то есть значения уничтожаются в конце выражения, в котором они созданы.
std::cout << 2 + 3 << '\n'; // 2 + 3 вычисляется как r-значение 5,
// которое уничтожается в конце этой инструкции
Однако, когда ссылка на константное значение инициализируется с помощью r-значения, время жизни r-значения увеличивается, чтобы соответствовать времени жизни ссылки.
int somefcn()
{
// обычно результат 2 + 3 имеет область видимости выражения и
// уничтожается в конце этой инструкции
const int& ref{ 2 + 3 };
// но поскольку результат теперь привязан к ссылке на константное значение...
std::cout << ref << '\n'; // мы можем использовать его здесь
} // и время жизни r-значения продлевается до этого момента,
// когда уничтожается константная ссылка
Константные ссылки как параметры функций
Ссылки, используемые в качестве параметров функции, также могут быть константными. Это позволяет нам получить доступ к аргументу, не делая его копии, и гарантируя, что функция не изменит значение, на которое указывает ссылка.
// ref - это константная ссылка на переданный аргумент, а не его копия
void changeN(const int& ref)
{
ref = 6; // недопустимо, ref - константная
}
Ссылки на константные значения особенно полезны в качестве параметров функции из-за их универсальности. Параметр константной ссылки позволяет передавать аргумент неконстантного l-значения, аргумент константного l-значения, литерал или результат выражения:
#include <iostream>
void printIt(const int& x)
{
std::cout << x;
}
int main()
{
int a{ 1 };
printIt(a); // неконстантное l-значение
const int b{ 2 };
printIt(b); // константное l-значение
printIt(3); // литеральное r-значение
printIt(2+b); // выражение, r-значение
return 0;
}
Приведенная выше программа печатает:
1234
Чтобы избежать ненужных, потенциально дорогостоящих копий, переменные, которые не являются указателями или базовыми типами данных (int
, double
и т.д.), обычно должны передаваться по (константной) ссылке. Базовые типы данных следует передавать по значению, если функции не требуется их изменять. Из этого правила есть несколько исключений, а именно типы, которые настолько малы, что процессору быстрее их копировать, чем выполнять дополнительное косвенное обращение через ссылку.
Напоминание
Ссылки действуют как указатели. Компилятор добавляет косвенное обращение, которое мы делали для указателя вручную с помощью звездочки.
Один из таких быстрых типов – std::string_view
. О других исключениях вы узнаете позже. Если вы не уверены, достаточно ли быстро передается небазовый тип по значению, передайте его по константной ссылке.
Лучшая практика
Передавайте переменные неуказателей, небазовых типов данных (например, структуры) по (константной) ссылке, если вы не знаете, что передача по значению выполняется быстрее.
Небольшой тест
Вопрос 1
Какой из следующих типов следует передавать по значению, а какой по константной ссылке? Предполагается, что функция, которая принимает переменные этих типов в качестве параметров, не изменяет их.
char
std::string
unsigned long
bool
- перечислитель
struct Position { double x{}; double y{}; double z{}; };
struct Player { int health{}; // Структура Player еще в разработке. // Позже будут добавлены еще члены. };
double
Для справки, вот как мы будем использоватьstruct ArrayView { const int* array{}; std::size_t length{}; };
ArrayView
:#include <cstddef> // std::size_t #include <iostream> #include <iterator> // std::size struct ArrayView { const int* value{}; std::size_t length{}; }; void fn(/* ??? */ array) { for (std::size_t i{ 0 }; i < array.length; ++i) { std::cout << array.value[i] << ' '; } } int main() { int array[3]{}; fn({ array, std::size(array) }); return 0; }
Ответ
char
– это базовый тип, его следует передавать по значению.std::string
должен создавать копию строки всякий раз, когда копируется. Передавайте его по константной ссылке.unsigned long
– это базовый тип, он должен передаваться по значению.bool
– это базовый тип, он должен передаваться по значению.- Перечислители (
enum
иenum class
) – это именованные целочисленные значения. Они основаны на базовом типе (обычноint
) и должны передаваться по значению.Position
– это пользовательский тип, который следует передавать по константной ссылке.- Хотя
Player
в текущем состоянии содержит только один член типаint
, что ускорит передачу по значению, но в будущем будет добавлено больше членов. Мы не хотим обновлять каждое использованиеPlayer
, когда это произойдет, поэтому мы передаем его по константной ссылке.double
– это базовый тип, он должен передаваться по значению.ArrayView
имеет только 2 переменных-члена, и в будущем новые члены добавляться не будут. РазмерArrayView
обычно будет составлять 16 байт в 64-битных системах. Хотя этот размер не идеален для процессора, но копироватьArrayView
всё же быстрее, чем передавать его по константной ссылке. Тип массива не имеет значения, потому что все указатели имеют одинаковый размер. Так же работаетstd::string_view
. В этом случаеArrayView::array
будетconst char*
. ПередачаArrayView
по константной ссылке тоже не будет ошибкой, потому что, если есть сомнения, константная ссылка – лучший вариант.