Что было бы лучше, если бы вы дали функции исходную переменную для работы:
unsigned long x = 4;
void func1(unsigned long& val) {
val = 5;
}
func1(x);
или же:
void func2(unsigned long* val) {
*val = 5;
}
func2(&x);
IOW: Есть ли причина выбирать одно вместо другого?
Как это согласуется с такой целью, как ссылочная прозрачность функционального программирования? Что делать, если вы всегда хотите, чтобы функции возвращали новые объекты и никогда не изменяли внутренне состояние, особенно переменных, переданных в функцию. Есть ли способ, которым эта концепция все еще используется с указателями и ссылками на таком языке, как C++? (Обратите внимание, я предполагаю, что у кого-то уже есть цель ссылочной прозрачности. Мне не интересно говорить о том, является ли это хорошей целью.)
Предпочитаю ссылки. Пользовательские указатели, когда у вас нет выбора.





Мое практическое правило:
Используйте указатели, если вы хотите выполнять с ними арифметические операции с указателями (например, увеличивая адрес указателя для перехода по массиву) или если вам когда-либо приходилось передавать NULL-указатель.
В противном случае используйте ссылки.
Отличный момент относительно того, что указатель равен NULL. Если у вас есть параметр указателя, вы должны либо явно проверить, не является ли он NULL, либо выполнить поиск всех случаев использования функции, чтобы убедиться, что он никогда не равен NULL. Это усилие не требуется для ссылок.
Объясните, что вы имеете в виду под арифметикой. Новый пользователь может не понять, что вы хотите изменить то, на что указывает указатель.
Мартин, Под арифметикой я подразумеваю, что вы передаете указатель на структуру, но знаете, что это не простая структура, а ее массив. В этом случае вы можете либо проиндексировать его, используя [], либо выполнить арифметику, используя ++ / - на указателе. Вот в чем разница.
Мартин, это можно делать только с указателями напрямую. Не со ссылками. Конечно, вы можете взять указатель на ссылку и сделать то же самое на практике, но если вы это сделаете, вы получите очень грязный код ...
А как насчет полиморфизма (например, Base* b = new Derived())? Это похоже на случай, с которым нельзя справиться без указателей.
@ChrisRedford Я экспериментально обнаружил, что с этим можно справиться либо с помощью указателей, либо с передачей по ссылке. Он просто передает значение, которое не работает (предположительно, потому что копирование данных из производного класса в базовый класс не слишком разумно)
Поскольку вы не можете хранить ссылки в контейнерах, лямбда-выражения будут иметь тенденцию принимать указатели, когда объекты не хранятся по значению.
Ссылки @RichardCorden не дают вам абсолютно никаких гарантий относительно действительности во время выполнения. Ваш код все еще может создавать UB, проблема будет семантически в другом месте, где программа плохо себя ведет во время выполнения. У вас нет возможности проверить во время выполнения, действительна ли ссылка, которую вы используете.
@martinkunev: Я не думаю, что подразумевал какие-либо гарантии. Как вы говорите, есть два отдельных вопроса: 1) действительный ли объект, 2) указатель NULL. Первый вопрос такой же для указателей и ссылок. Второй вопрос касается только указателей.
Я действительно думаю, что вам будет полезно установить следующие рекомендации по кодированию вызовов функций:
Как и во всех других местах, всегда указывайте соответствие const.
const.Передавать значение по указателю только в том случае, если значение 0 / NULL является допустимым вводом в текущем контексте.
Обоснование 1: Как звонящий, вы видите, что все, что вы передаете в должно быть, находится в рабочем состоянии.
Обоснование 2: Как называется, вы знаете, что все, что входит в является, находится в рабочем состоянии. Следовательно, для этого значения не требуется выполнять проверку NULL или обработку ошибок.
Обоснование 3: Обоснования 1 и 2 будут компилятор принудительно. По возможности всегда вылавливайте ошибки во время компиляции.
Если аргумент функции является выходным значением, передайте его по ссылке.
Выбирайте «передавать по значению» вместо «передавать по константной ссылке», только если значение является POD (Обычная старая структура данных) или достаточно маленьким (с точки зрения памяти) или другими способами, достаточно дешевыми (по времени) для копирования.
В нем отсутствует указание, когда: ... "когда использовать const &" ... Указание 2 должно быть написано "для [входных] значений, передавать по указателю только в том случае, если NULL является допустимым. В противном случае используйте ссылку на константу (или для" small "объекты, копия) или ссылку, если это значение [out]. Я слежу за этим сообщением, чтобы потенциально добавить +1.
Пункт 1 касается случая, который вы описываете.
Немного сложно передать выходной параметр по ссылке, если он не может быть сконструирован по умолчанию. Это довольно часто встречается в моем коде - вся причина, по которой функция создает этот выходной объект, заключается в том, что это нетривиально.
@MSalters: если вы собираетесь выделить память внутри функции (что, я думаю, вы имеете в виду), то почему бы просто не вернуть указатель на выделенную память?
@Kleist: От имени @MSalters есть много возможных причин. Во-первых, вы, возможно, уже выделили память для заполнения, например, предварительно заданный размер std::vector<>.
Указывая на, возможно, явно очевидное, бывают также случаи, когда вы хотите передать указатель с помощью ссылка, что, как и обычная ссылка, устраняет необходимость проверки NULL во внешнем указателе перед установкой указателя, на который он указывает к (потому что там не является внешним указателем). Конечно, вы должны в первую очередь использовать интеллектуальные указатели для всего этого (и передавать их по ссылке, если вы планируете изменить указанное значение).
@WhozCraig: Я стараюсь использовать Принцип наименьшего сюрприза и не добавлять «указатели передачи по ссылке», если только это не последний вариант в истории человечества ;-)
@JohannGerell Не могу тебя в этом винить.
1+, кроме того, что все, что вы пишете, имеет смысл, мне немного сложно увидеть в нем настоящий (отзывчивый) ответ.
@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.
@eznme: В качестве примера предположим, что у вас есть функция для установки «обработчика»: set_handler(handler* h), и документально подтверждено, что передача nullptr в качестве аргумента аналогична расчистка обработчику, тогда nullptr является допустимым в этом контексте. Однако то же самое можно было бы достичь, если бы установщик использовал ссылку, а затем имел отдельную функцию очистки без параметров. Если функция в первом случае вместо этого задокументировала, что обработчик не может быть очищен после его установки, то передача nullptr никогда не может быть допустимой в этом контексте.
@JohannGerell спасибо, что дало мне понять, что пункт 2.2 не сумасшедший, что иногда имеет смысл не проверять нулевое значение немедленно. Но, конечно (в вашем примере) проверка на нуль выполняется позже, прежде чем использовать обработчик.
Передайте по константной ссылке, если нет причины, по которой вы хотите изменить / сохранить передаваемое содержимое.
В большинстве случаев это будет наиболее эффективным методом.
Убедитесь, что вы используете const для каждого параметра, который не хотите изменять, поскольку это не только защищает вас от глупых действий с функцией, но и дает другим пользователям хорошее представление о том, что функция делает с переданными значениями. Это включает в себя создание указателя const, когда вы хотите изменить только то, на что указывает ...
В конечном итоге это оказывается субъективным. Обсуждение до сих пор полезно, но я не думаю, что на это есть правильный или решающий ответ. Многое будет зависеть от рекомендаций по стилю и ваших текущих потребностей.
Хотя есть несколько разных возможностей (независимо от того, может ли что-то быть NULL) с указателем, наибольшее практическое различие для выходного параметра - это чисто синтаксис. Руководство Google по стилю C++ (https://google.github.io/styleguide/cppguide.html#Reference_Arguments), например, требует только указателей для выходных параметров и разрешает только ссылки, которые являются константными. Причина заключается в удобочитаемости: что-то с синтаксисом значения не должно иметь семантического значения указателя. Я не утверждаю, что это обязательно правильно или неправильно, но я думаю, что дело в том, что это вопрос стиля, а не правильности.
Что означает, что ссылки имеют синтаксис значения, но семантическое значение указателя?
Похоже, вы передаете копию, поскольку часть «передать по ссылке» очевидна только из определения функции (синтаксиса значения), но вы не копируете передаваемое значение, вы по существу передаете указатель под капотом, что позволяет функция для изменения вашего значения.
Не следует забывать, что руководство по стилю Google C++ очень ненавидят.
Ссылка - это неявный указатель. В основном вы можете изменить значение, на которое указывает ссылка, но вы не можете изменить ссылку, чтобы указать на что-то еще. Итак, мои 2 цента заключаются в том, что если вы хотите изменить только значение параметра, передайте его как ссылку, но если вам нужно изменить параметр, чтобы он указывал на другой объект, передайте его с помощью указателя.
Вы должны передать указатель, если собираетесь изменить значение переменной. Несмотря на то, что технически передача ссылки или указателя одинаковы, передача указателя в вашем варианте использования более читабельна, поскольку она «рекламирует» тот факт, что значение будет изменено функцией.
Если вы следуете рекомендациям Иоганна Герелла, неконстантная ссылка также объявляет изменяемую переменную, поэтому указатель здесь не имеет этого преимущества.
@AlexanderKondratskiy: вы упускаете суть ... вы не можете сразу увидеть на месте звонка, принимает ли вызываемая функция параметр как ссылку const или не-const, но вы можете увидеть, прошел ли параметр ala &x против x, и используйте это соглашение для кодирования того, может ли параметр быть изменен. (Тем не менее, бывают случаи, когда вам нужно передать указатель const, поэтому соглашение - всего лишь намек. Спорные подозрения, что что-то может быть изменено, когда этого не произойдет, менее опасно, чем думать, что этого не произойдет, когда оно будет быть....)
Если у вас есть параметр, в котором вам может потребоваться указать отсутствие значения, обычно делают параметр значением указателя и передают значение 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 - без ссылок)
Используйте ссылку, когда можете, используйте указатель, когда вам нужно. От C++ FAQ: «Когда мне следует использовать ссылки, а когда - указатели?»
Указатели:
nullptr (или NULL).&, если ваш тип не является самим указателем,
делая явно, вы изменяете свой объект.Рекомендации:
&. Иногда это считается
плохо, потому что вы должны перейти к реализации функции, чтобы увидеть,
ваш параметр изменен.Небольшой замечание для тех, кто не знает: nullptr или NULL - это просто 0. stackoverflow.com/questions/462165/…
nullptr - это не то же самое, что 0. Попробуйте int a = nullptr; stackoverflow.com/questions/1282295/what-exactly-is-nullptr
Указатели
Указателю, который в настоящее время не указывает на действительную ячейку памяти, дается значение 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.
Ссылки, конечно, полезны, но я пришел из C, где указатели есть везде. Чтобы понять значение ссылок, нужно сначала уметь обращаться с указателями.