Подтвердить что ты не робот

Почему ссылки не переустанавливаются в С++

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

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

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

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

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

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

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

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

Это уже законный С++, но бессмысленный.

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

Приветствия, Феликс

4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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

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

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

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

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

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

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

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

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

Ответ 4

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

Что касается недействительности: вы не можете гарантировать, что такая "ссылочная ссылка" не является 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*.

Ответ 5

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

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

Ответ 6

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

Указатель - это просто переменная, которая содержит адрес памяти. У самого указателя есть собственный адрес памяти, и внутри этого адреса памяти он содержит другой адрес памяти, на который, как говорят, указывается. Ссылка не то же самое, у нее нет собственного адреса и, следовательно, она не может быть изменена на "удержание" другого адреса.

Я думаю, что parashift С++ FAQ по ссылкам говорит лучше:

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

и снова в FAQ 8.5:

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

Ответ 7

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

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

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

Ответ 8

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

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

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

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

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

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

Ответ 9

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

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

В примере jalf

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 new meaning
}

Ответ 10

Я бы предположил, что это связано с оптимизацией.

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

Ответ 11

Потому что иногда вещи нельзя переоценивать. (Например, ссылка на Singleton.)

Потому что отлично в функции знать, что ваш аргумент не может быть нулевым.

Но в основном, потому что он позволяет использовать то, что действительно является указателем, но которое действует как локальный объект значения. С++ пытается тяжело, чтобы процитировать Stroustrup, чтобы сделать экземпляры класса "делать как ints d". Передача int by vaue является дешевой, поскольку int помещается в машинный регистр. Классы часто больше, чем ints, и передача их по стоимости имеет значительные накладные расходы.

Возможность передать указатель (который часто является размером int или, возможно, двух ints), который "выглядит", объект значения позволяет нам писать более чистый код без "детализации реализации" разностей. И, наряду с перегрузкой оператора, он позволяет нам писать классы, используя синтаксис, аналогичный синтаксису, используемому с ints. В частности, это позволяет нам писать классы шаблонов с синтаксисом, которые могут быть одинаково применимы к примитивным, таким как ints и классы (например, класс Complex number).

И, особенно с перегрузкой оператора, есть места, мы должны вернуть объект, но опять же, гораздо дешевле вернуть указатель. Oncve снова, вернув ссылку, наш "выход".

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

char* p = s; *p = *s; *p++ = *s++; i = ++*p;

может быть запутанным.

Черт, спустя 40 лет C, люди до сих пор не могут даже согласиться, должно ли быть объявление указателя:

char* p;

или

char *p;

Ответ 12

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

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

Ответ 13

Я всегда задавался вопросом, почему они не сделали оператор ссылочного назначения (скажем: =) для этого.

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

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

Ответ 14

Быть наполовину серьезным: ИМХО, чтобы сделать их немного больше, чем указатели;) Вы знаете, что можете писать:

MyClass & c = *new MyClass();

Если вы еще позже можете написать:

c = *new MyClass("other")

Было бы целесообразно иметь какие-либо ссылки наряду с указателями?

MyClass * a =  new MyClass();
MyClass & b = *new MyClass();
a =  new MyClass("other");
b = *new MyClass("another");

Ответ 15

Тот факт, что ссылки в С++ не имеют значения NULL, является побочным эффектом их просто псевдонимов.

Ответ 17

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

#include <iostream>

struct Field_a_t
{
    int& a_;
    Field_a_t(int& a)
        : a_(a) {}
    Field_a_t& operator=(int& a)
    {
        // a_.~int(); // do this if you have a non-trivial destructor
        new(this)Field_a_t(a);
    }
};

struct MyType : Field_a_t
{
    char c_;
    MyType(int& a, char c)
        : Field_a_t(a)
        , c_(c) {}
};

int main()
{
    int i = 1;
    int j = 2;
    MyType x(i, 'x');
    std::cout << x.a_;
    x.a_ = 3;
    std::cout << i;
    ((Field_a_t&)x) = j;
    std::cout << x.a_;
    x.a_ = 4;
    std::cout << j;
}

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