Почему я не должен проверять, недопустимы ли ссылки/пустые?

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

В целом ссылки должны всегда быть действительными, потому что необходимо всегда инициализировать ссылку. Это означает, что, запрещая некоторые причудливые обстоятельства (см. ниже), можно быть уверены, что использование ссылки точно так же, как использует простую нессылочную переменную. Вы не должны проверять, чтобы удостовериться, что ссылка не указывает на ПУСТОЙ УКАЗАТЕЛЬ, и Вы не будете укушены неинициализированной ссылкой, для которой Вы забыли выделять память.

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

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

Кто-либо может пролить некоторый свет?

36
задан Smi 4 January 2013 в 11:38
поделиться

11 ответов

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

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

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

Вы можете выполнять проверку 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
72
ответ дан 27 November 2019 в 05:28
поделиться

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

4
ответ дан 27 November 2019 в 05:28
поделиться

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

1
ответ дан 27 November 2019 в 05:28
поделиться

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

Допустим, у вас есть:

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!

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

1
ответ дан 27 November 2019 в 05:28
поделиться

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

  • 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, если вы удалите объект, на который они ссылаются, победителя здесь нет;)

0
ответ дан 27 November 2019 в 05:28
поделиться

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



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 или что-то еще), но нет ничего, что могло бы работать постоянно.

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

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

5
ответ дан 27 November 2019 в 05:28
поделиться

Ссылки 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 - это просто & (независимо от псевдонима 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
ответ дан 27 November 2019 в 05:28
поделиться

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

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

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

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

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

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

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

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

4
ответ дан 27 November 2019 в 05:28
поделиться

Я думаю, это "зависит". Я знаю, это не ответ, но это действительно зависит. Я думаю, что защитное кодирование - это хорошая практика, которой стоит придерживаться. Если ваш стек трек имеет глубину 10 уровней, и любой сбой в трассировке приводит к сбою всей операции, то, конечно, проверяйте на верхнем уровне и позволяйте любым исключениям подниматься наверх. Но если вы можете восстановиться после того, как кто-то передаст вам нулевую ссылку, то проверка будет уместна. По моему опыту, когда мне приходится объединять код с другими компаниями для совместной интеграции, проверка (и протоколирование) всего на уровне публичного api позволяет вам отвести от себя указующие персты, которые случаются, когда интеграция идет не так, как ожидалось.

0
ответ дан 27 November 2019 в 05:28
поделиться

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

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

2
ответ дан 27 November 2019 в 05:28
поделиться

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

1
ответ дан 27 November 2019 в 05:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: