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

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

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

Е. Г. в следующем код:

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 (УСТАРЕВШИЙ):

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

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

18 ответов


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

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

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

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

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

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

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

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

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


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

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

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


с C++ FAQ Lite -

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

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

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

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


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

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

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


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

точка зрения Джона Кармака на эту тему аналогична:

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

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

изменить 2012-03-13

пользователей Брет Kuhns справедливо замечает:

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

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

например,std::unique_ptr и std::shared_ptr можно построить как" пустые " указатели через их значение по умолчанию конструктор:

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

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

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


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

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

add_one(&a);

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

add_one(a);

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

void add_one(int* const n);

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

foo(int* const a, int b);

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

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


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

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

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

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

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


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

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

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

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

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

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

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


скопировал из wiki -

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

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


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

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) конструктор передается ссылкой, поскольку он предотвращает передачу неправильных / нулевых указателей конструктору. Это как-то поощряет распределение переменных в стеке и получение преимуществ от РАИИ.

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


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

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

принимая во внимание мои нынешние правила следует.

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

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

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

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

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

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

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

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

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

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

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

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

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


точки, чтобы иметь в виду:

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

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

  3. указатель используется с * в то время как ссылки используются с &.

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

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

Указатель Vs Ссылка

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

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

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

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


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

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

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

здесь я думаю, что ссылки намного безопаснее, чем указатели. И я согласен с предыдущим высказыванием, что интерфейс (который должен быть четко документирован, см. design by contract, Bertrand Meyer) определяет результат параметров функции. Теперь принимая все это во внимание мои предпочтения идут использование ссылок везде, где / когда это возможно.


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


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

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

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

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

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


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


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

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