Другие языки были созданы для запрета указателей, неужели они ужасны?

Поначалу кажется, что C ++ все усложняет, вводя еще один уровень абстракции. Ссылки, кажется, инкапсулируют тот же набор функций, что и указатели. Обе эти конструкции используются для ссылки на другую сущность, поскольку они предоставляют точки доступа для управления содержимым сущности референт; они оба размещены в куче.

При этом мы сталкиваемся со следующими вопросами:

  1. Указатели и ссылки - это одно и то же?
  2. Существуют ли методы, которые могут выполнять только указатели, и наоборот?
  3. Когда мы предпочитаем указатели ссылкам и наоборот?

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

Указатели

Указатели представлены в C. Указатели хранят адрес памяти объекта типа T (где T - параметр шаблона, который может быть преобразован в любой тип), как уже упоминалось, он обеспечивает точку доступа для изменения значения объекта, на который он указывает. Рассмотрим следующий фрагмент:

Указатели отлично подходят для использования косвенных указаний, которые манипулируют содержимым в определенном блоке памяти. Давайте модифицируем приведенный выше код, чтобы продемонстрировать серию косвенных действий следующим образом:

Мы можем расширить это, вложив их вместе:

Как мы заметили, адрес указателя не совпадает с адресом его ссылки. Это наблюдение связано с тем, что указатели имеют собственную идентичность. Следовательно, указатели могут быть переназначены другой переменной, чтобы они могли допускать нулевые значения: NULL и nullptr (предпочтительнее использовать nullptr, если вы используете указатели). То есть int *x = nullptr действителен и будет компилироваться.

Более того, указатели могут выполнять итерацию по массивам с операторами пре- и пост-инкремента, операторами пре- и пост-декремента и операторами индекса.

Как мы заметили, адрес указателя не совпадает с адресом его ссылки. Это наблюдение связано с тем, что указатели имеют собственную идентичность. Следовательно, указатели могут быть переназначены другой переменной, чтобы они могли допускать нулевые значения: NULL и nullptr (предпочтительнее использовать nullptr, если вы используете указатели). То есть int *x = nullptr действительно и будет компилироваться.

Более того, указатели могут выполнять итерацию по массивам с операторами пре- и пост-инкремента, операторами пре- и пост-декремента и операторами индекса.

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

Напоминания об использовании указателей

  • Указателями может быть сложно управлять, поскольку их можно вкладывать и комбинировать без ограничений, что может привести к сложному фрагменту кода, который трудно поддерживать.
  • Указатели могут привести к утечке ресурсов, если им не управлять должным образом: вы должны обеспечить очистку, когда типы указателей больше не нужны, и вы должны не забыть освободить ресурсы в куче, когда они больше не имеют отношения к вашему коду.
  • Предпочитайте использование интеллектуальных указателей: std::unique_ptr<T> u_ptr, std::shared_ptr<T> s_ptr и std::weak_ptr<T> w_ptr вместо сырых указателей, когда производительность является вторичной по отношению к безопасности или нет необходимости в более тонком контроле. Интеллектуальные указатели позволяют автоматизировать очистку с помощью RAII с минимальными затратами на производительность, в случае u_ptr накладные расходы на производительность отсутствуют.
  • Для объектов-указателей ptr_obj или определяемых пользователем типов доступ к членам можно получить с помощью оператора стрелки, например. ptr_obj-> function().

использованная литература

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

Теперь, когда мы знаем об указателях, как нам противопоставить это ссылкам?

Ссылки можно рассматривать как постоянный указатель T const* c_ptr = &obj, и его не следует путать с const T* ref = &obj: первый позволяет изменять содержимое объекта, но ограничивает адрес ссылкой на obj, последний отменяет эффект и ограничивает только obj быть измененным.

Чтобы изменить содержимое x и установить для него y, мы говорим:

Тот же эффект достигается и со ссылками. В результате ссылки привязываются к местоположению объекта в памяти. Он позволяет изменять содержимое объекта, но его нельзя переназначить, и он должен быть привязан при инициализации. Следовательно, ссылка на сущность предполагает сущность исходной переменной.

Как уже упоминалось, ссылки могут также выделять память для одного объекта в куче.

Напоминания об использовании ссылок

  • Ссылки не могут быть присвоены nullptr.
  • Ссылки не могут перебирать массивы.
  • Возврат ссылок на локальные переменные может вызвать неопределенное поведение (висячие ссылки).
  • Ссылки должны быть привязаны к существующей сущности.
  • Ссылки допускают только один уровень косвенного обращения.
  • Ссылки выражают намерение ясно и лаконично.
  • Начиная со стандартов C ++ 11, ссылки стали использоваться чаще, поскольку элементы семантики перемещения построены из понятия ссылок (lvalue и rvalue ссылки).

Резюме

  • Указатели и ссылки - это одно и то же? Нет.
  • Существуют ли методы, которые могут выполнять только указатели, и наоборот? Указатели более гибкие, чем ссылки, которые могут усложнить ваш код, если им не управлять. T const* ref обеспечивает такое же поведение ссылок, за исключением ссылок на массив, что дает нам больше причин для выбора ссылок вместо указателей (когда это необходимо), поскольку его семантика ограничивает итерацию по структурам, подобным массивам:

Тогда как индексация массива со ссылками приведет к ошибке компилятора:

  • Когда мы предпочитаем указатели ссылкам и наоборот? Типы указателей обычно полезны для настройки отдельного поля косвенного обращения, способного изменять содержимое нескольких переменных одного и того же типа. Наличие одного и того же указателя для доступа к нескольким переменным может быть трудным для отслеживания и рассуждений. Желательно сохранить ясность по сравнению с удобством ввода одного указателя на все переменные одного и того же типа. По этой причине использование ссылок безопаснее.

Указатели на самом деле не страшны, на самом деле они являются одной из величайших функций, которые мы открываем в таких языках, как C / C ++, однако их сила имеет свою цену.

В заключение, ссылки - это не просто синтаксический сахар для приведения T const*, у них есть свои собственные цели дизайна и цель, а именно содержать адрес объявленных объектов. Это улучшает ясность наших намерений, и в таких ситуациях компилятор C ++ - наш друг.

Первоначально опубликовано на https://dcode.hashnode.dev/

  • Когда мы предпочитаем указатели ссылкам и наоборот? Типы указателей обычно полезны для настройки отдельного поля косвенного обращения, способного изменять содержимое нескольких переменных одного и того же типа. Наличие одного и того же указателя для доступа к нескольким переменным может быть трудным для отслеживания и рассуждений. Желательно сохранить ясность по сравнению с удобством ввода одного указателя на все переменные одного и того же типа. По этой причине использование ссылок безопаснее.

Указатели на самом деле не страшны, на самом деле они являются одной из величайших функций, которые мы открываем в таких языках, как C / C ++, однако их сила имеет свою цену.

В заключение, ссылки - это не просто синтаксический сахар для приведения T const*, у них есть свои собственные цели дизайна и цель, а именно содержать адрес объявленных сущностей. Это улучшает ясность наших намерений, и в таких ситуациях компилятор C ++ - наш друг.