9.17 – Ссылки и const

Добавлено 9 июня 2021 в 07:41
Глава 9 – Массивы, строки, указатели и ссылки  (содержание)

Ссылка на константное значение

Так же, как можно объявить указатель на константное значение, можно объявить и ссылку на константное значение. Это делается путем объявления ссылки с помощью ключевого слова 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

Какой из следующих типов следует передавать по значению, а какой по константной ссылке? Предполагается, что функция, которая принимает переменные этих типов в качестве параметров, не изменяет их.

  1. char
  2. std::string
  3. unsigned long
  4. bool
  5. перечислитель
  6. struct Position
    {
      double x{};
      double y{};
      double z{};
    };
  7. struct Player
    {
      int health{};
      // Структура Player еще в разработке. 
      // Позже будут добавлены еще члены.
    };
  8. double
  9. 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;
    }

  1. char – это базовый тип, его следует передавать по значению.
  2. std::string должен создавать копию строки всякий раз, когда копируется. Передавайте его по константной ссылке.
  3. unsigned long – это базовый тип, он должен передаваться по значению.
  4. bool – это базовый тип, он должен передаваться по значению.
  5. Перечислители (enum и enum class) – это именованные целочисленные значения. Они основаны на базовом типе (обычно int) и должны передаваться по значению.
  6. Position – это пользовательский тип, который следует передавать по константной ссылке.
  7. Хотя Player в текущем состоянии содержит только один член типа int, что ускорит передачу по значению, но в будущем будет добавлено больше членов. Мы не хотим обновлять каждое использование Player, когда это произойдет, поэтому мы передаем его по константной ссылке.
  8. double – это базовый тип, он должен передаваться по значению.
  9. ArrayView имеет только 2 переменных-члена, и в будущем новые члены добавляться не будут. Размер ArrayView обычно будет составлять 16 байт в 64-битных системах. Хотя этот размер не идеален для процессора, но копировать ArrayView всё же быстрее, чем передавать его по константной ссылке. Тип массива не имеет значения, потому что все указатели имеют одинаковый размер. Так же работает std::string_view. В этом случае ArrayView::array будет const char*. Передача ArrayView по константной ссылке тоже не будет ошибкой, потому что, если есть сомнения, константная ссылка – лучший вариант.

Теги

C++ / Cppconstl-value / l-значениеLearnCppr-value / r-значениеДля начинающихОбучениеПрограммированиеСсылка / Reference (программирование)

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.