Современный C++ использует множество модификаторов, и они ошеломляют, пока вы не наберетесь опыта. Сегодня вы узнаете о постоянных ссылках и о том, когда их следует использовать.

TL;DR

Постоянные ссылки выглядят так:

void my_function(const std::string& my_reference) { ... }
  • Используйте их при передаче больших неизменяемых значений в функции.
  • Если функции необходимо изменить копию данных, не используйте ссылку.
  • Если функции необходимо изменить исходные данные, используйте непостоянную ссылку.

Что такое ссылки?

std::string my_value = "Hello, World!";
std::string& my_reference = my_value; // This is a reference
void my_function(std::string& arg) { ... }

Обратите внимание, что мы определяем ссылку, помещая амперсанд (&) между типом и именем переменной.

Ссылки по сути являются указателями с парой дополнительных функций:

  1. Ссылки не могут быть NULL.
  2. Ссылки не могут быть изменены с помощью арифметики.
  3. Ссылки не требуют операторов ссылки (&), разыменования (*) или доступа к указателю (->).

Думайте о них как о причудливых указателях, менее гибких, но, возможно, более безопасных.

Что такое постоянные ссылки?

std::string my_value = "Hello, World!";
const std::string& my_reference = my_value;
void my_function(const std::string& arg) { ... }
  1. Значение, на которое указывают постоянные ссылки, не может быть изменено.
  2. Можно вызывать только методы, отмеченные const.

Нарушение любого из этих правил вызовет ошибку во время компиляции:

void my_func(const std::string& my_reference)
{
    my_reference = "Test string"; // COMPILER ERROR
    my_reference.pop_back(); // COMPILER ERROR
}

void my_func2(std::string& my_reference)
{
    my_reference = "Test string"; // OK
    my_reference.pop_back(); // OK
}

Пометка ссылки как const (квази-) гарантирует вам и другим разработчикам, что вы не изменяете переменные неожиданно.

Когда следует использовать постоянные ссылки?

Рассмотрим следующие две функции:

int this_function_copies(std::string value)
{
    return value.size();
}

int this_function_references(const std::string& value)
{
    return value.size();
}

Первая функция копирует значение исходной строки в новый объект и при каждом вызове вызывает его конструктор.

Вторая функция значительно быстрее, потому что передается только «значение указателя». Это означает, что строку не нужно копировать или реконструировать.

Используйте постоянные ссылки при передаче больших значений или структур в качестве аргументов, если вы не хотите изменять содержимое.

Общие типы значений, передаваемые как ссылки, включают, но не ограничиваются:

  1. Струны
  2. Карты
  3. Векторы
  4. Пользовательские классы и структуры

Как правило, нет смысла передавать ссылки на данные, которые меньше или равны sizeof(void*), потому что размер копируемых данных одинаков.

Когда я не должен использовать постоянные ссылки?

Избегайте постоянных ссылок, если вы хотите изменить данные, передаваемые функции.

Передайте по значению, если вы не хотите изменять исходное значение:

void this_function_copies(std::string value)
{
    value = "This is a new string";
}

int main()
{
    std::string my_string = "Hello, World!";

    this_function_copies(my_string);
    
    std::cout << my_string << std::endl;
    return 0;
}
// OUTPUT: "Hello, World!"

Передайте непостоянную ссылку, если вы хотите изменить исходное значение:

void this_function_references(std::string& value)
{
    value = "This is a new string";
}

int main()
{
    std::string my_string = "Hello, World!";

    this_function_references(my_string);

    std::cout << my_string << std::endl;
    return 0;
}
// OUTPUT: "This is a new string"

В чем разница между постоянными ссылками и постоянными указателями?

Помимо синтаксических различий, существует одно существенное различие между постоянными указателями и ссылками, о котором следует помнить.

  1. Ссылки на константы не могут быть NULL, а это означает, что ваши функции не нуждаются в преамбулах проверки нулей.
class MyClass {};

int main()
{
    const MyClass& my_class_ref = nullptr; // COMPILER ERROR
    const MyClass* my_class_p = nullptr; // OK

    return 0;
}

Если значение должно быть необязательным (т. е. nullptr является допустимым значением), то вы можете обернуть ссылку на константу в std::reference_wrapper и поместить ее внутрь std::optional, но простой постоянный указатель может быть более элегантным и читаемым.

Каковы предостережения?

Отбрасывание константности

И ссылки на константы, и указатели на константы могут быть приведены к неконстантным типам и видоизменены. В большинстве случаев это приведет к неопределенному поведению, и этого следует избегать.

Для полноты картины вот пример использования const_cast<>, но, пожалуйста, воздержитесь от его использования без веской причины:

void this_function_references(const std::string& value)
{
    const_cast<std::string&>(value) = "This is a new string";
}

int main()
{
    std::string my_string = "Hello, World!";

    this_function_references(my_string);

    std::cout << my_string << std::endl;
    return 0;
}
// OUTPUT: "This is a new string"
// Compiled with AppleClang 13.1.6.13160021 on macOS 12.2.1 (M1)

В зависимости от вашей цепочки инструментов вы можете получить другой результат или даже ошибку сегментации. Будьте предупреждены.