Почему ссылки не восстанавливаются в C ++ - PullRequest
67 голосов
/ 08 апреля 2009

C ++ ссылки имеют два свойства:

  • Они всегда указывают на один и тот же объект.
  • Они не могут быть 0.

Указатели противоположны:

  • Они могут указывать на разные объекты.
  • Они могут быть 0.

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

Edit: Вопрос возникает часто, потому что я обычно использую ссылки, когда хочу убедиться, что «ассоциация» (я избегаю слов «ссылка» или «указатель» здесь) никогда не будет недействительной.

Я не думаю, что когда-либо думал, что "здорово, что эта ссылка всегда относится к одному и тому же объекту". Если бы ссылки были перезагружаемыми, можно было бы получить текущее поведение, подобное этому:

int i = 3;
int& const j = i;

Это уже допустимый C ++, но бессмысленный.

Я повторяю свой вопрос следующим образом: «Что послужило основанием для« ссылки » - это объектный дизайн? Почему считалось полезным иметь ссылки всегда быть тем же объектом, а не только когда объявлен как const? "

Ура, Феликс

Ответы [ 17 ]

86 голосов
/ 08 апреля 2009

Причина, по которой C ++ не позволяет перепривязывать ссылки, приведена в «Построении и эволюции C ++» Страуструпа:

Невозможно изменить то, на что ссылается ссылка после инициализации. Таким образом, после инициализации ссылки на C ++ невозможно сделать ссылку на другой объект позже; это не может быть повторно связано. Раньше меня укусили ссылки Algol68, где r1=r2 может либо назначить через r1 объект, на который ссылаются, либо присвоить новое значение ссылки r1 (повторное связывание r1) в зависимости от типа r2. Я хотел избежать таких проблем в C ++.

32 голосов
/ 08 апреля 2009

В C ++ часто говорят, что «ссылка - это объект». В некотором смысле это так: хотя ссылки и обрабатываются как указатели при компиляции исходного кода, ссылка предназначена для обозначения объекта, который не копируется при вызове функции. Поскольку ссылки не имеют прямой адресации (например, ссылки не имеют адреса и возвращают адрес объекта), семантически не имеет смысла переназначать их. Более того, в C ++ уже есть указатели, которые обрабатывают семантику переустановки.

18 голосов
/ 08 апреля 2009

Потому что тогда у вас не будет повторяемого типа, который не может быть 0. Если вы не включили 3 типа ссылок / указателей. Что только усложнит язык из-за небольшого выигрыша (и тогда почему бы не добавить 4-й тип тоже?

Лучший вопрос может быть такой: почему вы хотите, чтобы ссылки были перезаписываемыми? Если бы они были, это сделало бы их менее полезными во многих ситуациях. Компилятору будет сложнее выполнять анализ псевдонимов.

Кажется, что основная причина, по которой ссылки в Java или C # являются перезаписываемыми, заключается в том, что они выполняют работу указателей. Они указывают на объекты. Они не являются псевдонимами для объекта.

Каким должен быть эффект следующего?

int i = 42;
int& j = i;
j = 43;

В сегодняшнем C ++ с невосстановимыми ссылками это просто. j является псевдонимом для i, и i заканчивается значением 43.

Если бы ссылки были перезаписываемыми, тогда третья строка связала бы ссылку j с другим значением. Это больше не псевдоним i, а целочисленный литерал 43 (что, конечно, недопустимо). Или, возможно, более простой (или, по крайней мере, синтаксически правильный) пример:

int i = 42;
int k = 43;
int& j = i;
j = k;

С многоразовыми ссылками. j будет указывать на k после оценки этого кода. В C ++ не повторяемые ссылки, j по-прежнему указывает на i, и i присваивается значение 43.

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

5 голосов
/ 08 апреля 2009

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

Указатель - это просто переменная, которая содержит адрес памяти. Сам указатель имеет собственный адрес памяти, и внутри этого адреса памяти он содержит еще один адрес памяти , на который он указывает. Ссылка - это не то же самое, у нее нет собственного адреса, и, следовательно, ее нельзя изменить, чтобы "хранить" другой адрес. Я думаю, что parashift C ++ FAQ по ссылкам говорит это лучше всего:

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

и снова в FAQ 8.5 :

В отличие от указателя, если ссылка привязан к объекту, он не может быть «пересаживается» на другой объект. ссылка сама по себе не является объектом (это не имеет идентичности; взяв адрес ссылка дает вам адрес референт; помните: ссылка является его референтом).

5 голосов
/ 08 апреля 2009

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

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

struct null_pointer_exception { ... };

template<typename T>
struct non_null_pointer {
    // No default ctor as it could only sensibly produce a NULL pointer
    non_null_pointer(T* p) : _p(p) { die_if_null(); }
    non_null_pointer(non_null_pointer const& nnp) : _p(nnp._p) {}
    non_null_pointer& operator=(T* p) { _p = p; die_if_null(); }
    non_null_pointer& operator=(non_null_pointer const& nnp) { _p = nnp._p; }

    T& operator*() { return *_p; }
    T const& operator*() const { return *_p; }
    T* operator->() { return _p; }

    // Allow implicit conversion to T* for convenience
    operator T*() const { return _p; }

    // You also need to implement operators for +, -, +=, -=, ++, --

private:
    T* _p;
    void die_if_null() const {
        if (!_p) { throw null_pointer_exception(); }
    }
};

Иногда это может быть полезно - функция, принимающая параметр non_null_pointer<int>, безусловно, сообщает вызывающей стороне больше информации, чем функция, принимающая int*.

4 голосов
/ 08 апреля 2009

Вероятно, было бы менее запутанным назвать C ++ ссылками "псевдонимами"? Как уже упоминалось, ссылки в C ++ должны иметь как переменную , на которую они ссылаются, а не как указатель / ссылка на переменную. Таким образом, я не могу придумать вескую причину, по которой они должны быть сбрасываемыми.

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

3 голосов
/ 10 февраля 2015

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

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

  • Указатели позволяют вам сделать две вещи: изменить значение за указателем (с помощью оператора -> или *) и изменить сам указатель (прямое назначение =). Пример:

    int a;
    int * p = &a;
    1. Изменение значения требует разыменования: *p = 42;
    2. Изменение указателя: p = 0;
  • Ссылки позволяют изменять только значение. Зачем? Поскольку нет другого синтаксиса для выражения переустановки. Пример: * 1 023 *

    int a = 10;
    int b = 20;
    int & r = a;
    r = b; // re-set r to b, or set a to 20?

Другими словами, было бы неоднозначно, если бы вам было разрешено повторно установить ссылку. Это имеет еще больший смысл при передаче по ссылке:

void foo(int & r)
{
    int b = 20;
    r = b; // re-set r to a? or set a to 20?
}
void main()
{
    int a = 10;
    foo(a);
}

Надеюсь, это поможет: -)

2 голосов
/ 08 апреля 2009

C ++ ссылки могут иногда быть принудительно равными 0 с некоторыми компиляторами (это просто плохая идея *, и это нарушает стандарт *).

int &x = *((int*)0); // Illegal but some compilers accept it

РЕДАКТИРОВАТЬ: согласно различным людям, которые знают стандарт намного лучше, чем я, приведенный выше код производит "неопределенное поведение". По крайней мере, в некоторых версиях GCC и Visual Studio я видел, как это делает ожидаемую вещь: эквивалент установки указателя на NULL (и вызывает обращение к указателю NULL при обращении к нему).

1 голос
/ 08 апреля 2009

Вы не можете сделать это:

int theInt = 0;
int& refToTheInt = theInt;

int otherInt = 42;
refToTheInt = otherInt;

... по той же причине, почему secondInt и firstInt не имеют здесь одно и то же значение:

int firstInt = 1;
int secondInt = 2;
secondInt = firstInt;
firstInt = 3;

assert( firstInt != secondInt );
1 голос
/ 30 мая 2013

На самом деле это не ответ, а обходной путь для этого ограничения.

По сути, когда вы пытаетесь «перепривязать» ссылку, вы на самом деле пытаетесь использовать то же имя для ссылки на новое значение в следующем контексте. В C ++ этого можно достичь, введя область видимости блока.

В примере с Джальфом

int i = 42;
int k = 43;
int& j = i;
//change i, or change j?
j = k;

если вы хотите изменить i, напишите это, как указано выше. Однако, если вы хотите изменить значение j на значение k, вы можете сделать это:

int i = 42;
int k = 43;
int& j = i;
//change i, or change j?
//change j!
{
    int& j = k;
    //do what ever with j's new meaning
}
...