Почему мне не нужно проверять, являются ли ссылки недействительными / нулевыми? - PullRequest
36 голосов
/ 26 марта 2010

Чтение http://www.cprogramming.com/tutorial/references.html, это говорит:

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

Мой вопрос: откуда мне знать, что память объекта не была освобождена / удалена ПОСЛЕ того, как вы инициализировали ссылку.

Что сводится к тому, что я не могу принять этот совет о вере, и мне нужно лучшее объяснение.

Может кто-нибудь пролить свет?

Ответы [ 11 ]

72 голосов
/ 26 марта 2010

Вы не можете знать, являются ли ссылки недействительными:

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

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

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

int *p = 0;
int &r = *p;//no one does this
if(&r != 0)//and so no one does this kind of check
{
}

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

Возможно, вы захотите использовать ссылки в таких случаях:

//I want the function fn to not make a copy of cat and to use
// the same memory of the object that was passed in
void fn(Cat &cat)
{
   //Do something with cat
}

//...main...
Cat c;
fn(c);

Трудно выстрелить себе в ногу со ссылками:

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

Например:

int *p;
if(true)
{
  int x;
  p = &x;
}

*p = 3;//runtime error

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

Вы все еще можете выстрелить себе в ногу со ссылками, но вы ДОЛЖНЫ попытаться это сделать.

Например:

int *p = new int;
*p = 3;
int &r = *p;
delete p;
r = 3;//runtime error
5 голосов
/ 26 марта 2010

Вы не можете. Вы также не можете с указателем. Рассмотрим:



struct X
{
  int * i;
  void foo() { *i++; }
};

int main()
{
  int *i = new int(5);
  X x = { i };
  delete i;
  x.foo();
}

Теперь, какой код вы можете поместить в X :: foo (), чтобы убедиться, что указатель i все еще действителен?

Ответ: стандартная проверка не проводится. Существуют некоторые приемы, которые могут работать с msvc в режиме отладки (проверка на наличие 0xfeeefeee или чего-либо еще), но нет ничего, что будет работать последовательно.

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

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

4 голосов
/ 26 марта 2010

У меня вопрос: откуда мне знать, что память объекта не была освобождена / удалена ПОСЛЕ того, как вы инициализировали ссылку.

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

Что касается ссылок, то же самое относится и к тому, что у вас нет способа определить, ссылается ли он на объект, который все еще действителен. Однако в C ++ не существует такой вещи, как «нулевая ссылка», поэтому нет необходимости проверять, является ли ссылка «нулевой».

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

Что сводится к тому, что я не могу принять этот совет о вере, и мне нужно лучшее объяснение

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

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

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

3 голосов
/ 26 марта 2010

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

2 голосов
/ 26 марта 2010

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

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

1 голос
/ 26 марта 2010

C ++ ссылки являются псевдонимами. В результате этого разыменование указателей не обязательно происходит там, где они появляются, они происходят там, где они оцениваются. Взятие ссылки на объект не оценивает объект, а псевдоним его. Использование ссылки - это то, что оценивает объект. C ++ не может гарантировать, что ссылки действительны; если это произойдет, все компиляторы C ++ будут повреждены. Единственный способ сделать это - исключить все возможности динамического размещения со ссылками. На практике предполагается, что ссылка является действительным объектом. Поскольку * NULL не определено и недопустимо, из этого следует, что для p = NULL * p также не определено. Проблема с C ++ в том, что * p будет эффективно передан функции или задержан в ее оценке до того момента, когда ссылка фактически будет использована. Утверждение, что оно не определено, не является вопросом вопроса аскера. Если бы это было незаконно, компилятор обеспечил бы его, как и стандарт. То же самое я не знаю.

int &r = *j; // aliases *j, but does not evaluate j
if(r > 0) // evaluates r ==> *j, resulting in dereference (evaluate j) at this line, not what some expect
  ;

1) Вы можете проверить ссылку для наложения псевдонима NULL, & r это просто & (независимо от псевдонима) (EDIT)

2) При передаче «разыменованного» указателя (* i) в качестве параметра ссылки разыменование не происходит на месте вызова, оно может никогда не произойти, потому что это ссылка (ссылки являются псевдонимами, а не оценками). Это оптимизация ссылок. Если они были оценены в месте вызова, либо компилятор вставляет дополнительный код, либо это будет вызов по значению и менее производительный, чем указатель.

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

#include <iostream>

int fun(int & i) {
   std::cerr << &i << "\n";
   std::cerr << i << "\n"; // crash here
}

int main() {
   int * i = new int();
   i = 0;
   fun(*i); // Why not crash here? Because the deref doesn't happen here, inconsistent, but critical for performance of references
}

РЕДАКТИРОВАТЬ: изменил мой пример, поскольку он был неправильно истолкован как предложение для тестирования ссылок, а не то, что я хотел продемонстрировать. Я только хотел продемонстрировать недействительную ссылку.

1 голос
/ 26 марта 2010

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

1 голос
/ 26 марта 2010

Нет синтаксиса для проверки правильности ссылки. Вы можете проверить указатель на NULL, но для ссылки нет действительного / недействительного теста. Конечно, указанный объект может быть освобожден или перезаписан каким-либо ошибочным кодом. Та же ситуация для указателей: если указатель не NULL указывает на освобожденный объект, вы не можете проверить это.

1 голос
/ 26 марта 2010

Потому что к тому времени, как вы туда доберетесь, вы наверняка совершите неопределенное поведение.Позвольте мне объяснить:)

Скажем, у вас есть:

void fun(int& n);

Теперь, если вы передадите что-то вроде:

int* n = new int(5);
fun(*n); // no problems until now!

Но если вы сделаете следующее:

int* n = new int(5);
...
delete n;
...
fun(*n); // passing a deleted memory!

Когда вы достигнете fun, вы будете разыменовывать * n, что является неопределенным поведением, если указатель удален, как в примере выше.Таким образом, пути нет, и теперь должен быть путь, потому что принятие правильных параметров - это целая точка ссылок.

0 голосов
/ 26 марта 2010

Я думаю, вы могли бы извлечь выгоду из простого параллелизма:

  • T & аналогично T * const
  • T const & аналогично T const * const

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

Теперь, чтобы ответить на ваш вопрос: да, возможно, ссылка является нулевой или недействительной. Вы можете проверить нулевую ссылку (T& t = ; if (&t == 0)), но это не должно произойти >> по контракту ссылка действительна.

Когда использовать ссылку против указателя? Используйте указатель, если:

  • Вы хотите иметь возможность сменить пуанти
  • Вы хотите выразить возможную ничтожность

В любом другом случае используйте ссылку.

Некоторые примеры:

// Returns an object corresponding to the criteria
// or a special value if it cannot be found
Object* find(...); // returns 0 if fails

// Returns an object corresponding to the criteria
// or throw "NotFound" if it cannot be found
Object& find(...); // throw NotFound

Передача аргументов:

void doSomething(Object* obj)
{
  if (obj) obj->doSomething();
}

void doSomething(Object& obj) { obj.do(); obj.something(); }

Атрибуты:

struct Foo
{
  int i;
  Bar* b; // No constructor, we need to initialize later on
};

class Foo
{
public:
  Foo(int i, Bar& b): i(i), b(b) {}
private:
  int i;
  Bar& b; // will always point to the same object, Foo not Default Constructible
};

class Other
{
public:
  Other(Bar& b): b(&b) {} // NEED to pass a valid object for init

  void swap(Other& rhs);  // NEED a pointer to be able to exchange

private:
  Bar* b;
};

Функциональные ссылки и указатели играют ту же роль. Это просто вопрос контракта. И, к сожалению, оба могут вызвать Undefined Behavior, если вы удалите объект, на который они ссылаются, там нет победителя;)

...