Указатель против ссылки

Что было бы лучше, если бы вы дали функции исходную переменную для работы:

unsigned long x = 4;

void func1(unsigned long& val) {
     val = 5;            
}
func1(x);

или же:

void func2(unsigned long* val) {
     *val = 5;
}
func2(&x);

IOW: Есть ли причина выбирать одно вместо другого?

Ссылки, конечно, полезны, но я пришел из C, где указатели есть везде. Чтобы понять значение ссылок, нужно сначала уметь обращаться с указателями.

Jay D 08.05.2012 23:43

Как это согласуется с такой целью, как ссылочная прозрачность функционального программирования? Что делать, если вы всегда хотите, чтобы функции возвращали новые объекты и никогда не изменяли внутренне состояние, особенно переменных, переданных в функцию. Есть ли способ, которым эта концепция все еще используется с указателями и ссылками на таком языке, как C++? (Обратите внимание, я предполагаю, что у кого-то уже есть цель ссылочной прозрачности. Мне не интересно говорить о том, является ли это хорошей целью.)

ely 30.09.2013 21:31

Предпочитаю ссылки. Пользовательские указатели, когда у вас нет выбора.

Ferruccio 05.07.2014 17:14
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
260
3
113 806
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

Ответ принят как подходящий

Мое практическое правило:

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

В противном случае используйте ссылки.

Отличный момент относительно того, что указатель равен NULL. Если у вас есть параметр указателя, вы должны либо явно проверить, не является ли он NULL, либо выполнить поиск всех случаев использования функции, чтобы убедиться, что он никогда не равен NULL. Это усилие не требуется для ссылок.

Richard Corden 22.09.2008 15:10

Объясните, что вы имеете в виду под арифметикой. Новый пользователь может не понять, что вы хотите изменить то, на что указывает указатель.

Martin York 22.09.2008 20:30

Мартин, Под арифметикой я подразумеваю, что вы передаете указатель на структуру, но знаете, что это не простая структура, а ее массив. В этом случае вы можете либо проиндексировать его, используя [], либо выполнить арифметику, используя ++ / - на указателе. Вот в чем разница.

Nils Pipenbrinck 25.11.2008 23:35

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

Nils Pipenbrinck 25.11.2008 23:38

А как насчет полиморфизма (например, Base* b = new Derived())? Это похоже на случай, с которым нельзя справиться без указателей.

Chris Redford 07.03.2013 23:25

@ChrisRedford Я экспериментально обнаружил, что с этим можно справиться либо с помощью указателей, либо с передачей по ссылке. Он просто передает значение, которое не работает (предположительно, потому что копирование данных из производного класса в базовый класс не слишком разумно)

chbaker0 12.12.2013 06:02

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

user2672165 08.10.2014 15:19

Ссылки @RichardCorden не дают вам абсолютно никаких гарантий относительно действительности во время выполнения. Ваш код все еще может создавать UB, проблема будет семантически в другом месте, где программа плохо себя ведет во время выполнения. У вас нет возможности проверить во время выполнения, действительна ли ссылка, которую вы используете.

martinkunev 27.02.2016 20:48

@martinkunev: Я не думаю, что подразумевал какие-либо гарантии. Как вы говорите, есть два отдельных вопроса: 1) действительный ли объект, 2) указатель NULL. Первый вопрос такой же для указателей и ссылок. Второй вопрос касается только указателей.

Richard Corden 29.02.2016 19:11

Я действительно думаю, что вам будет полезно установить следующие рекомендации по кодированию вызовов функций:

  1. Как и во всех других местах, всегда указывайте соответствие const.

    • Примечание. Это означает, среди прочего, что только исходящие значения (см. Пункт 3) и значения, передаваемые по значению (см. Пункт 4), могут не иметь спецификатора const.
  2. Передавать значение по указателю только в том случае, если значение 0 / NULL является допустимым вводом в текущем контексте.

    • Обоснование 1: Как звонящий, вы видите, что все, что вы передаете в должно быть, находится в рабочем состоянии.

    • Обоснование 2: Как называется, вы знаете, что все, что входит в является, находится в рабочем состоянии. Следовательно, для этого значения не требуется выполнять проверку NULL или обработку ошибок.

    • Обоснование 3: Обоснования 1 и 2 будут компилятор принудительно. По возможности всегда вылавливайте ошибки во время компиляции.

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

    • Обоснование: мы не хотим нарушать пункт 2 ...
  4. Выбирайте «передавать по значению» вместо «передавать по константной ссылке», только если значение является POD (Обычная старая структура данных) или достаточно маленьким (с точки зрения памяти) или другими способами, достаточно дешевыми (по времени) для копирования.

    • Обоснование: Избегайте ненужных копий.
    • Примечание: достаточно мал и достаточно дешево не являются абсолютными измеримыми.

В нем отсутствует указание, когда: ... "когда использовать const &" ... Указание 2 должно быть написано "для [входных] значений, передавать по указателю только в том случае, если NULL является допустимым. В противном случае используйте ссылку на константу (или для" small "объекты, копия) или ссылку, если это значение [out]. Я слежу за этим сообщением, чтобы потенциально добавить +1.

paercebal 22.09.2008 15:54

Пункт 1 касается случая, который вы описываете.

Johann Gerell 22.09.2008 17:14

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

MSalters 22.09.2008 19:02

@MSalters: если вы собираетесь выделить память внутри функции (что, я думаю, вы имеете в виду), то почему бы просто не вернуть указатель на выделенную память?

Kleist 16.02.2011 12:27

@Kleist: От имени @MSalters есть много возможных причин. Во-первых, вы, возможно, уже выделили память для заполнения, например, предварительно заданный размер std::vector<>.

Johann Gerell 16.02.2011 13:28

Указывая на, возможно, явно очевидное, бывают также случаи, когда вы хотите передать указатель с помощью ссылка, что, как и обычная ссылка, устраняет необходимость проверки NULL во внешнем указателе перед установкой указателя, на который он указывает к (потому что там не является внешним указателем). Конечно, вы должны в первую очередь использовать интеллектуальные указатели для всего этого (и передавать их по ссылке, если вы планируете изменить указанное значение).

WhozCraig 02.01.2013 07:11

@WhozCraig: Я стараюсь использовать Принцип наименьшего сюрприза и не добавлять «указатели передачи по ссылке», если только это не последний вариант в истории человечества ;-)

Johann Gerell 02.01.2013 22:56

@JohannGerell Не могу тебя в этом винить.

WhozCraig 02.01.2013 23:41

1+, кроме того, что все, что вы пишете, имеет смысл, мне немного сложно увидеть в нем настоящий (отзывчивый) ответ.

Wolf 19.05.2014 16:14

@JohannGerell, это немного сбивает с толку: "Only pass a value by pointer if the value 0/NULL is a valid input in the current context", особенно с учетом того, что его называют usable state. Вы имеете в виду, что NULL находится в рабочем состоянии? В первом вы говорите, что NULL является допустимым вводом, но в обосновании 2 вы говорите, что сама функция не будет проверять, является ли Obj * NULL.

Bernd Elkemann 16.01.2015 21:14

@eznme: В качестве примера предположим, что у вас есть функция для установки «обработчика»: set_handler(handler* h), и документально подтверждено, что передача nullptr в качестве аргумента аналогична расчистка обработчику, тогда nullptr является допустимым в этом контексте. Однако то же самое можно было бы достичь, если бы установщик использовал ссылку, а затем имел отдельную функцию очистки без параметров. Если функция в первом случае вместо этого задокументировала, что обработчик не может быть очищен после его установки, то передача nullptr никогда не может быть допустимой в этом контексте.

Johann Gerell 18.01.2015 00:55

@JohannGerell спасибо, что дало мне понять, что пункт 2.2 не сумасшедший, что иногда имеет смысл не проверять нулевое значение немедленно. Но, конечно (в вашем примере) проверка на нуль выполняется позже, прежде чем использовать обработчик.

Bernd Elkemann 18.01.2015 11:21

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

В большинстве случаев это будет наиболее эффективным методом.

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

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

Хотя есть несколько разных возможностей (независимо от того, может ли что-то быть NULL) с указателем, наибольшее практическое различие для выходного параметра - это чисто синтаксис. Руководство Google по стилю C++ (https://google.github.io/styleguide/cppguide.html#Reference_Arguments), например, требует только указателей для выходных параметров и разрешает только ссылки, которые являются константными. Причина заключается в удобочитаемости: что-то с синтаксисом значения не должно иметь семантического значения указателя. Я не утверждаю, что это обязательно правильно или неправильно, но я думаю, что дело в том, что это вопрос стиля, а не правильности.

Что означает, что ссылки имеют синтаксис значения, но семантическое значение указателя?

Eric Andrew Lewis 16.08.2015 16:43

Похоже, вы передаете копию, поскольку часть «передать по ссылке» очевидна только из определения функции (синтаксиса значения), но вы не копируете передаваемое значение, вы по существу передаете указатель под капотом, что позволяет функция для изменения вашего значения.

phant0m 19.05.2016 13:35

Не следует забывать, что руководство по стилю Google C++ очень ненавидят.

Deduplicator 20.12.2018 03:54

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

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

Если вы следуете рекомендациям Иоганна Герелла, неконстантная ссылка также объявляет изменяемую переменную, поэтому указатель здесь не имеет этого преимущества.

Alexander Kondratskiy 19.07.2011 17:39

@AlexanderKondratskiy: вы упускаете суть ... вы не можете сразу увидеть на месте звонка, принимает ли вызываемая функция параметр как ссылку const или не-const, но вы можете увидеть, прошел ли параметр ala &x против x, и используйте это соглашение для кодирования того, может ли параметр быть изменен. (Тем не менее, бывают случаи, когда вам нужно передать указатель const, поэтому соглашение - всего лишь намек. Спорные подозрения, что что-то может быть изменено, когда этого не произойдет, менее опасно, чем думать, что этого не произойдет, когда оно будет быть....)

Tony Delroy 17.06.2015 10:29

Если у вас есть параметр, в котором вам может потребоваться указать отсутствие значения, обычно делают параметр значением указателя и передают значение NULL.

В большинстве случаев лучшим решением (с точки зрения безопасности) является использование boost :: optional. Это позволяет передавать необязательные значения по ссылке, а также в качестве возвращаемого значения.

// Sample method using optional as input parameter
void PrintOptional(const boost::optional<std::string>& optional_str)
{
    if (optional_str)
    {
       cout << *optional_str << std::endl;
    }
    else
    {
       cout << "(no string)" << std::endl;
    }
}

// Sample method using optional as return value
boost::optional<int> ReturnOptional(bool return_nothing)
{
    if (return_nothing)
    {
       return boost::optional<int>();
    }

    return boost::optional<int>(42);
}

Рассмотрим ключевое слово C# out. Компилятор требует, чтобы вызывающий метод применял ключевое слово out к любым аргументам out, даже если он уже знает, есть ли они. Это сделано для повышения читабельности. Хотя в современных IDE я склонен думать, что это работа по выделению синтаксиса (или семантики).

опечатка: семантический, а не симантический; +1 Согласен с возможностью выделения вместо записи (C#) или & (в случае C - без ссылок)

peenut 25.06.2011 13:23

Используйте ссылку, когда можете, используйте указатель, когда вам нужно. От C++ FAQ: «Когда мне следует использовать ссылки, а когда - указатели?»

Указатели:

  • Может быть присвоен nullptr (или NULL).
  • На сайте вызова вы должны использовать &, если ваш тип не является самим указателем, делая явно, вы изменяете свой объект.
  • Указатели можно отсканировать.

Рекомендации:

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

Небольшой замечание для тех, кто не знает: nullptr или NULL - это просто 0. stackoverflow.com/questions/462165/…

Serguei Fedorov 09.01.2014 09:41

nullptr - это не то же самое, что 0. Попробуйте int a = nullptr; stackoverflow.com/questions/1282295/what-exactly-is-nullptr

Johan Lundberg 27.04.2015 21:58

Указатели

  • Указатель - это переменная, которая содержит адрес памяти.
  • Объявление указателя состоит из базового типа, символа * и имени переменной.
  • Указатель может указывать на любое количество переменных за время жизни
  • Указателю, который в настоящее время не указывает на действительную ячейку памяти, дается значение null (которое равно нулю)

    BaseType* ptrBaseType;
    BaseType objBaseType;
    ptrBaseType = &objBaseType;
    
  • & - это унарный оператор, который возвращает адрес памяти своего операнда.

  • Оператор разыменования (*) используется для доступа к значению, хранящемуся в переменной, на которую указывает указатель.

       int nVar = 7;
       int* ptrVar = &nVar;
       int nVar2 = *ptrVar;
    

Ссылка

  • Ссылка (&) похожа на псевдоним существующей переменной.

  • Ссылка (&) похожа на постоянный указатель, ссылка на который автоматически разыменовывается.

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

  • Ссылка должна быть инициализирована при ее создании.

  • После инициализации ссылки на объект ее нельзя изменить для ссылки на другой объект.

  • У вас не может быть ссылок NULL.

  • Ссылка на константу может относиться к константе int. Это делается с помощью временной переменной со значением const

    int i = 3;    //integer declaration
    int * pi = &i;    //pi points to the integer i
    int& ri = i;    //ri is refers to integer i – creation of reference and initialization
    

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

Ссылки особенно полезны для указания аргументов функции.

для получения дополнительной информации см. "Путешествие по C++" Бьярна Страуструпа (2014). Страницы 11-12.

Другие вопросы по теме