Когда использовать ссылки против указателей

Я понимаю синтаксис и общую семантику указателей и ссылок, но как мне решить, когда более или менее целесообразно использовать ссылки или указатели в 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++)? Существуют ли какие-либо проблемы с производительностью с одним или другим?

EDIT (OUTDATED):

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

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

Ответ 1

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

Из С++ FAQ Lite -

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

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

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

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

Ответ 4

Мое правило:

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

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

Ответ 5

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 6

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

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

add_one(&a);

может быть более четким, чем

add_one(a);

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

void add_one(int* const n);

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

foo(int* const a, int b);

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

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

Ответ 7

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

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

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

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

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

Ответ 8

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

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

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

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

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

Здесь имя функции дает понять, что вы возвращаете информацию в массив. Поэтому нет путаницы.

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

Ответ 9

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

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

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

Ответ 10

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

class SimCard
{
    public:
        explicit SimCard(int id):
            m_id(id)
        {
        }

        int getId() const
        {
            return m_id;
        }

    private:
        int m_id;
};

class RefPhone
{
    public:
        explicit RefPhone(const SimCard & card):
            m_card(card)
        {
        }

        int getSimId()
        {
            return m_card.getId();
        }

    private:
        const SimCard & m_card;
};

Вначале может показаться хорошей идеей иметь параметр в конструкторе RefPhone(const SimCard & card), переданный ссылкой, потому что он предотвращает передачу неправильных/нулевых указателей на конструктор. Это как-то поощряет выделение переменных в стеке и получение преимуществ от RAII.

PtrPhone nullPhone(0);  //this will not happen that easily
SimCard * cardPtr = new SimCard(666);  //evil pointer
delete cardPtr;  //muahaha
PtrPhone uninitPhone(cardPtr);  //this will not happen that easily

Но затем временные люди приходят разрушить ваш счастливый мир.

RefPhone tempPhone(SimCard(666));   //evil temporary
//function referring to destroyed object
tempPhone.getSimId();    //this can happen

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

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


Существуют следующие различия между указателями и ссылками.

  • Когда дело доходит до передачи переменных, pass by reference выглядит как pass by value, но имеет семантику указателя (действует как указатель).
  • Ссылка не может быть напрямую инициализирована до 0 (null).
  • Ссылка (ссылка, объект без ссылки) не может быть изменена (эквивалентна указателю "* const" ).
  • const ссылается на временный параметр.
  • Локальные ссылки на константу продлевают срок службы временных объектов

Учитывая это, мои текущие правила заключаются в следующем.

  • Используйте ссылки для параметров, которые будут использоваться локально в пределах области функций.
  • Используйте указатели, когда 0 (null) является допустимым значением параметра, или вам нужно сохранить параметр для дальнейшего использования. Если 0 (null) допустимо, я добавляю суффикс "_n" к параметру, используйте защищенный указатель (например QPointer в Qt) или просто документирую его. Вы также можете использовать интеллектуальные указатели. Вы должны быть более осторожны с общими указателями, чем с обычными указателями (в противном случае вы можете столкнуться с утечками памяти и ошибкой памяти).

Ответ 11

Следует иметь в виду:

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

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

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

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

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

Указатель Vs Reference

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);

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

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

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

Ответ 12

Ниже приведены некоторые рекомендации.

Функция использует переданные данные без изменения:

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

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

  • Если объект данных является структурой хорошего размера, используйте указатель const или константу ссылка на повышение эффективности программы. Вы сохраняете время и пространство, необходимые для копировать структуру или дизайн класса. Сделайте указатель или ссылку const.

  • Если объект данных является объектом класса, используйте ссылку const. Семантика дизайна класса часто требует использования ссылки, что является основной причиной добавления С++ эта функция. Таким образом, стандартный способ передачи аргументов объекта класса является ссылкой.

Функция изменяет данные в вызывающей функции:

1.Если объект данных является встроенным типом данных, используйте указатель. Если вы укажете код как fixit (& x), где x является int, его довольно ясно, что эта функция намеревается изменить x.

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

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

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

Конечно, это только рекомендации, и могут быть причины для выбор. Например, cin использует ссылки для базовых типов, так что вы можете использовать cin → n вместо cin → & n.

Ответ 13

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

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

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

Ответ 14

Просто положил мой копейки. Я только что провел тест. Обманчивый. Я просто позволю g++ создать файлы сборки одной и той же мини-программы с помощью указателей по сравнению с использованием ссылок. При просмотре вывода они точно такие же. Кроме имени символа. Поэтому, глядя на производительность (на простом примере), проблем нет.

Теперь о теме указателей и ссылок. ИМХО. Я считаю, что чистота стоит прежде всего. Как только я прочитал неявное поведение, мои пальцы начинают скручиваться. Я согласен, что это хорошее неявное поведение, что ссылка не может быть NULL.

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

Здесь я считаю, что ссылки гораздо безопаснее, чем указатели. И я согласен с предыдущим утверждением, что интерфейс (который должен быть четко документирован, см. Проект по контракту, Бертран Мейер) определяет результат параметров для функции. Теперь, принимая во внимание все мои предпочтения, переходим к используя ссылки везде, где это возможно.

Ответ 15

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

Ответ 16

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

int *pInt = new int; // allocates *pInt on the Heap

Ответ 17

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

Ответ 18

Я предпочитаю использовать указатели. По крайней мере, ясно, что вы делаете. У меня такое ощущение, что ссылки в основном используются из-за STL и его синтаксических последствий для кода. Из-за этого также так много стандартных новинок библиотеки С++, таких как std:: move....., чтобы получить именно то, что вы хотите, а не то, что вы интуитивно подумали бы.

Ответ 19

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

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