Когда использовать ссылки против указателей - PullRequest
356 голосов
/ 14 августа 2011

Я понимаю синтаксис и общую семантику указателей по сравнению со ссылками, но как мне решить, когда более или менее целесообразно использовать ссылки или указатели в API?

Естественно, в некоторых ситуациях требуется один илидругой (operator++ нужен ссылочный аргумент), но в целом я предпочитаю использовать указатели (и константные указатели), поскольку синтаксис ясен, что переменные передаются деструктивно.

Например, вследующий код:

void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
  int a = 0;
  add_one(a); // Not clear that a may be modified
  add_one(&a); // 'a' is clearly being passed destructively
}

С указателем всегда (более) очевидно, что происходит, поэтому для API и т. п., где ясность представляет большую проблему, указатели не более уместны, чем ссылки?Означает ли это, что ссылки должны использоваться только при необходимости (например, operator++)?Есть ли какие-либо проблемы с производительностью одного или другого?

РЕДАКТИРОВАТЬ (УСТАРЕЛО):

Помимо разрешения значений NULL и работы с необработанными массивами, кажется, что выбор сводится к личным предпочтениям.Я согласился с приведенным ниже ответом, который ссылается на Руководство по стилю Google C ++ , поскольку в нем представлено мнение, что «Ссылки могут вводить в заблуждение, так как они имеют синтаксис значений, но семантику указателей».

Из-зачто касается дополнительной работы, необходимой для очистки аргументов указателя, которые не должны быть равны NULL (например, add_one(0) вызовет версию указателя и прервется во время выполнения), имеет смысл с точки зрения удобства обслуживания использовать ссылки, в которых ДОЛЖЕН присутствовать объект, хотя этоСтыдно терять синтаксическую ясность.

Ответы [ 15 ]

272 голосов
/ 14 августа 2011

Используйте ссылки, где вы можете, указатели, где вы должны.

Избегайте указателей, пока не сможете.

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

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

Например, возврат указателя на объект является допустимой опцией, когда функция может возвращать nullptr в некоторых случаях, и предполагается, что это будет. Тем не менее, лучшим вариантом будет использовать что-то похожее на boost::optional.

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

В вашем примере нет смысла использовать указатель в качестве аргумента, потому что:

  1. если вы укажите nullptr в качестве аргумента, вы попадете в undefined-поведение-land;
  2. версия справочного атрибута не позволяет (без простых уловок) проблемы с 1.
  3. версию справочного атрибута для пользователя проще понять: вы должны предоставить действительный объект, а не то, что может быть нулевым.

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

58 голосов
/ 14 августа 2011

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

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

Некоторые соглашения о кодировании (например, Google's ) предписывают, чтовсегда следует использовать указатели или константные ссылки, потому что ссылки имеют немного неясный синтаксис: они имеют ссылочное поведение, но синтаксис значения.

31 голосов
/ 14 августа 2011

С C ++ FAQ Lite -

Используйте ссылки, когда можете, и указатели, когда нужно.

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

Исключением из приведенного выше является то, где параметр функции или возвращение значение нуждается в ссылке «страж» - ссылка, которая не ссылается к объекту. Обычно это лучше всего сделать, вернув / взяв указатель, и придание указателю NULL этого особого значения (ссылки должны всегда псевдоним объектов, а не разыменованный указатель NULL).

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

15 голосов
/ 19 мая 2013

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

  • Используйте указатели для исходящих или входящих / исходящих параметров. Таким образом, можно увидеть, что значение будет изменено. (Вы должны использовать &)
  • Используйте указатели, если параметр NULL является допустимым значением. (Убедитесь, что это const, если это входящий параметр)
  • Используйте ссылки для входящего параметра, если он не может быть NULL и не является примитивным типом (const T&).
  • Используйте указатели или умные указатели при возврате вновь созданного объекта.
  • Используйте указатели или умные указатели в качестве членов структуры или класса вместо ссылок.
  • Использовать ссылки для псевдонимов (например, int &current = someArray[i])

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

12 голосов
/ 28 февраля 2013

Отказ от ответственности: кроме того факта, что ссылки не могут быть NULL или "отскок" (то есть, вы не можете изменить объект, псевдоним которого они имеют), это действительно сводится к вопросу вкуса, поэтому я нескажу "это лучше".

Тем не менее, я не согласен с вашим последним утверждением в посте, так как я не думаю, что код теряет ясность со ссылками.В вашем примере

add_one(&a);

может быть яснее, чем

add_one(a);

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

void add_one(int* const n);

также несколько неясна: n будет единственным целым числом или массивом?Иногда у вас есть доступ только к (плохо документированным) заголовкам, а подписи типа

foo(int* const a, int b);

не так просто интерпретировать с первого взгляда.

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

12 голосов
/ 13 сентября 2012

Как и другие уже отвечавшие: всегда используйте ссылки, если только переменная NULL / nullptr не является действительно допустимым состоянием.

Точка зрения Джона Кармака на эту тему похожа:

NULL-указатели являются самой большой проблемой в C / C ++, по крайней мере, в нашем коде. Двойное использование одного значения как флага и адреса вызывает невероятное количество фатальных проблем. С ++ ссылки должны быть предпочтительнее указателей, когда это возможно; в то время как ссылка «действительно» является просто указателем, она имеет неявный контракт, не являющийся NULL. Выполняйте NULL-проверки, когда указатели превращаются в ссылки, после этого вы можете игнорировать проблему.

http://www.altdevblogaday.com/2011/12/24/static-code-analysis/

Редактировать 2012-03-13

Пользователь Брет Кюнс справедливо замечает:

Стандарт C ++ 11 был доработан. Я думаю, что пора в этой теме упомянуть, что большая часть кода должна отлично работать с комбинацией ссылок, shared_ptr и unique_ptr.

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

Например, std::unique_ptr и std::shared_ptr могут быть сконструированы как «пустые» указатели через конструктор по умолчанию:

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

А потом у нас есть забавная проблема: «Как мы передаем умный указатель в качестве параметра функции?»

Jon ' ответ на вопрос C ++ - передача ссылок на boost :: shared_ptr , и следующие комментарии показывают, что даже тогда, передавая смарт указатель по копии или по ссылке не такой четкий, как хотелось бы (по умолчанию я предпочитаю «по ссылке», но могу ошибаться).

7 голосов
/ 28 августа 2013

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

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

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

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

Чтобы безопасно хранить ссылку на элемент коллекции, необходимо использовать умный указатель с подсчетом ссылок.

5 голосов
/ 14 августа 2011

Любая разница в производительности будет настолько мала, что не будет оправдано использование менее понятного подхода.

Во-первых, один случай, который не был упомянут, когда ссылки, как правило, превосходят, - это const ссылки. Для непростых типов передача const reference избегает создания временного и не вызывает путаницы, о которой вы беспокоитесь (поскольку значение не изменяется). В этом случае принуждение человека передать указатель вызывает замешательство, которое вас беспокоит, так как просмотр адреса, взятого и переданного функции, может заставить вас думать, что значение изменилось.

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

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

bool GetFooArray(array &foo); // my preference
bool GetFooArray(array *foo); // alternative

Здесь имя функции дает понять, что вы получаете информацию обратно в массив. Так что нет путаницы.

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

4 голосов
/ 15 августа 2011

Скопировано из wiki -

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

Я согласен на 100% с этим, и поэтому я считаю, что вы должны использовать ссылку только тогда, когда выесть очень веская причина для этого.

3 голосов
/ 02 сентября 2018

Имейте в виду:

  1. Указатели могут быть NULL, ссылки не могут быть NULL.

  2. Ссылки проще в использовании, const можно использовать для ссылки, когда мы не хотим изменять значение и нам просто нужна ссылка в функции.

  3. Указатель используется с *, а ссылки используются с &.

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

  5. Вы можете иметь указатели на тип void int a=5; void *p = &a;, но не можете иметь ссылку на тип void.

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

void fun(int *a)
{
    cout<<a<<'\n'; // address of a = 0x7fff79f83eac
    cout<<*a<<'\n'; // value at a = 5
    cout<<a+1<<'\n'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
    cout<<*(a+1)<<'\n'; // value here is by default = 0
}
void fun(int &a)
{
    cout<<a<<'\n'; // reference of original a passed a = 5
}
int a=5;
fun(&a);
fun(a);

Вердикт, когда что использовать

Указатель : для массива, списка ссылок, реализаций дерева и арифметики указателя.

Ссылка : В параметрах функции и типах возврата.

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