Прямой ответ
Что такое ссылка в C ++? Некоторый конкретный экземпляр типа, который не является типом объекта .
Что такое указатель в C ++? Некоторый конкретный экземпляр типа, который является типом объекта .
Начиная с определения типа объекта ISO C ++ :
Тип объекта - это (возможно, cv -квалифицированный) тип, который не является типом функции, не является ссылочным типом и не cv void.
Может быть важно знать, что тип объекта является категорией верхнего уровня юниверса типа в C ++. Справочник также является категорией верхнего уровня. Но указатель не.
Указатели и ссылки упоминаются вместе в контексте составной тип . Это в основном связано с природой синтаксиса объявления, унаследованного от (и расширенного) C, который не имеет ссылок. (Кроме того, начиная с C ++ 11 существует более одного вида деклараторов ссылок, в то время как указатели все еще «единообразны»: &
+ &&
против *
.) Таким образом, проектируется язык, специфичный для «расширения», с похожими стиль C в этом контексте несколько разумен. (Я все еще буду утверждать, что синтаксис деклараторов тратит впустую синтаксическую выразительность много , расстраивает как пользователей, так и реализации. Таким образом, все они не квалифицируются как встроенные в новом дизайне языка. Это совершенно другая тема о дизайне PL.)
В противном случае незначительно, чтобы указатели можно было квалифицировать как специфические типы типов со ссылками вместе. Они просто имеют слишком мало общих свойств, кроме синтаксического сходства, поэтому в большинстве случаев нет необходимости их объединять.
Обратите внимание, что в приведенных выше утверждениях упоминаются только "указатели" и "ссылки" как типы. Есть несколько интересных вопросов об их экземплярах (например, переменные). Там также приходит слишком много заблуждений.
Различия категорий верхнего уровня уже могут выявить множество конкретных различий, не связанных напрямую с указателями:
- Типы объектов могут иметь квалификаторы верхнего уровня
cv
. Ссылки не могут.
- Переменная типов объектов занимает память согласно семантике абстрактной машины . Ссылка не обязательно занимает память (подробности см. Ниже в разделе о заблуждениях).
- ...
Еще несколько специальных правил для ссылок:
- Составные объявления являются более строгими в отношении ссылок.
- Ссылки могут свернуть .
- Специальные правила для
&&
параметров (как «пересылка ссылок»), основанные на свертывании ссылок во время вывода параметров шаблона, позволяют «идеальная пересылка» параметров.
- Ссылки имеют особые правила при инициализации. Время жизни переменной, объявленной как ссылочный тип, может отличаться от обычных объектов через расширение.
- Кстати, некоторые другие контексты, такие как инициализация, включающие
std::initializer_list
, следуют некоторым аналогичным правилам продления срока службы эталона. Это еще одна банка червей.
- ...
Заблуждения
Я знаю, что ссылки - это синтаксический сахар, поэтому код легче читать и писать.
Технически, это совершенно неправильно. Ссылки не являются синтаксическим сахаром каких-либо других функций в C ++, потому что они не могут быть точно заменены другими функциями без каких-либо семантических различий.
(Точно так же лямбда-выражения s являются не синтаксическим сахаром любых других функций в C ++, поскольку его нельзя точно смоделировать с помощью "неопределенных" свойств, таких как порядок объявления захваченных переменных , что может быть важно, потому что порядок инициализации таких переменных может быть значительным.)
C ++ имеет только несколько видов синтаксических сахаров в этом строгом смысле. Одним из экземпляров является (унаследованный от C) встроенный (не перегруженный) оператор []
, который определен точно с такими же семантическими свойствами конкретных форм комбинирования, что и встроенный оператор унарный *
и двоичный +
.
Хранение
Итак, указатель и ссылка используют одинаковый объем памяти.
Вышеприведенное утверждение просто неверно. Чтобы избежать таких заблуждений, взгляните на правила ISO C ++:
С [intro.object] / 1 :
... Объект занимает область хранения в период его строительства, на протяжении всей его жизни и в период его разрушения. ...
С [dcl.ref] / 4 :
Не указано, требуется ли ссылка для хранения.
Обратите внимание, что это семантические свойства.
Прагматик
Даже если указатели недостаточно квалифицированы, чтобы их можно было объединить со ссылками в смысле языкового дизайна, все еще есть некоторые аргументы, делающие спорным выбор между ними в некоторых других контекстах, например, при выборе параметров типы.
Но это не вся история. Я имею в виду, есть больше вещей, чем указатели против ссылок, которые вы должны учитывать.
Если вам не нужно придерживаться такого чрезмерно специфического выбора, в большинстве случаев ответ будет коротким: вам не нужно использовать указатели, поэтому вы не . Указатели, как правило, достаточно плохие, потому что они подразумевают слишком много вещей, которых вы не ожидаете, и они будут полагаться на слишком много неявных предположений, подрывающих удобство сопровождения и (даже) переносимость кода. Излишне полагаться на указатели - это определенно плохой стиль, и его следует избегать в смысле современного C ++. Пересмотреть свою цель, и вы, наконец, обнаружите, что указатель является функцией последних сортов в большинство случаев.
- Иногда языковые правила явно требуют использования определенных типов. Если вы хотите использовать эти функции, соблюдайте правила.
- Конструкторам копирования требуются определенные типы cv -
&
ссылочного типа в качестве 1-го типа параметра. (И обычно он должен быть const
квалифицированным.)
- Для конструкторов перемещения требуются определенные типы cv -
&&
ссылочного типа в качестве первого типа параметра. (И обычно не должно быть определителей.)
- Для определенных перегрузок операторов требуются ссылочные или не ссылочные типы. Например:
- Перегружено
operator=
, поскольку для специальных функций-членов требуются ссылочные типы, аналогичные 1-му параметру конструкторов копирования / перемещения.
- Постфикс
++
требуется пустышка int
.
- ...
- Если вы знаете, что передача по значению (т. Е. Использование нереферентных типов) достаточна, используйте ее напрямую, особенно при использовании реализации, поддерживающей обязательное копирование в C ++ 17. ( Предупреждение : Однако до исчерпывающе причина необходимости может быть очень сложной .)
- Если вы хотите использовать некоторые маркеры с правами собственности, используйте умные указатели, такие как
unique_ptr
и shared_ptr
(или даже сами доморощенные, если вы хотите, чтобы они были непрозрачные ), а не необработанные указатели.
- Если вы выполняете некоторые итерации по диапазону, используйте итераторы (или некоторые диапазоны, которые еще не предоставлены стандартной библиотекой), а не необработанные указатели, если вы не уверены, что необработанные указатели будут работать лучше (например, для меньших зависимостей заголовка) в очень специфических случаях.
- Если вы знаете, что передачи по значению достаточно, и вам нужна какая-то явная обнуляемая семантика, используйте обертку, например
std::optional
, а не необработанные указатели.
- Если вы знаете, что передача по значению не идеальна по вышеуказанным причинам, и вам не нужна семантика, допускающая обнуляемость, используйте {lvalue, rvalue, forwarding} -references.
- Даже если вам нужна семантика, такая как традиционный указатель, часто есть что-то более подходящее, например
observer_ptr
в Library Fundamental TS.
Единственные исключения нельзя обойти на текущем языке:
- Когда вы реализуете умные указатели выше, вам, возможно, придется иметь дело с необработанными указателями.
- Для определенных процедур взаимодействия языков требуются указатели, например
operator new
. (Тем не менее, cv - void*
все еще довольно отличается и безопаснее по сравнению с обычными объектными указателями, потому что он исключает неожиданную арифметику указателей, если вы не полагаетесь на какое-то несоответствующее расширение в void*
, как в GNU.)
- Указатели на функции могут быть преобразованы из лямбда-выражений без перехватов, а ссылки на функции - нет. Вы должны использовать указатели функций в неуниверсальном коде для таких случаев, даже если вы намеренно не хотите обнуляемых значений.
Итак, на практике ответ так очевиден: когда сомневаешься, избегай указателей . Вы должны использовать указатели только тогда, когда есть очень явные причины, по которым нет ничего более подходящего. За исключением нескольких исключительных случаев, упомянутых выше, такие варианты почти всегда не являются специфичными только для C ++ (но, скорее всего, для конкретной реализации языка). Такими примерами могут быть:
- Вы должны использовать API старого стиля (C).
- Вы должны соответствовать требованиям ABI определенных реализаций C ++.
- Вы должны взаимодействовать во время выполнения с различными языковыми реализациями (включая различные сборки, языковую среду выполнения и FFI некоторых высокоуровневых клиентских языков) на основе допущений конкретных реализаций.
- Вы должны повысить эффективность перевода (компиляция и компоновка) в некоторых крайних случаях.
- В некоторых крайних случаях вы должны избегать раздувания символов.
Предостережения в отношении языковой нейтральности
Если вы пришли к вопросу через какой-то результат поиска Google (не относится к C ++) , это, скорее всего, будет неправильное место.
Ссылки в C ++ довольно «странные», так как по сути они не первоклассные: они будут рассматриваться как объекты или функции, на которые ссылаются , поэтому у них нет шансов поддержать некоторые первые Операции класса, такие как левый операнд оператор доступа к члену независимо от типа упомянутого объекта. Другие языки могут иметь или не иметь аналогичные ограничения на свои ссылки.
Ссылки в C ++, скорее всего, не сохранят значения в разных языках. Например, ссылки в общем случае не подразумевают ненулевые свойства для значений, как в C ++, поэтому такие допущения могут не работать в некоторых других языках (и вы легко найдете контрпримеры, например, Java, C #, ...).
В целом ссылки на разные языки программирования могут быть общими, но давайте оставим это для некоторых других вопросов в SO.
(Дополнительное примечание: вопрос может быть значимым раньше, чем участвуют любые "C-подобные" языки, например ALGOL 68 против PL / I .)