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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Вы можете делать 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

Ответ 2

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



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

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

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

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

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

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

Ответ 3

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

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

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

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

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

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

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

Скажите, что у вас есть:

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, который является undefined, если указатель удален, как в приведенном выше примере. Таким образом, нет никакого способа, и теперь должно быть действительно так, потому что предполагать действительные параметры - это вся ссылка.

Ответ 7

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

Ответ 8

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

Ответ 9

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

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) в качестве эталонного параметра разыгрывание не происходит на колл-сайте, это может никогда не произойти, потому что это ссылка (ссылки - это псевдонимы, а не оценки). Это оптимизация ссылок. Если они были оценены в callsite, либо компилятор вставляет дополнительный код, либо это будет вызов по значению и менее результативен, чем указатель.

Да, сама ссылка не 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
}

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

Ответ 10

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

Ответ 11

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

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