为什么我不需要检查引用是否无效/ null?

时间:2010-03-26 16:23:50

标签: c++ reference null

阅读http://www.cprogramming.com/tutorial/references.html,它说:

  

一般情况下,应始终引用   是有效的,因为你必须永远   初始化参考。这意味着   除非有些离奇   情况(见下文),你可以   确定使用引用只是   喜欢使用普通的旧的非引用   变量。你不需要检查   确保参考不是   指向NULL,你不会得到   被未初始化的引用所咬   你忘了分配内存了   对

我的问题是我怎么知道在你初始化参考文献后没有释放/删除对象的内存。

归结为我不能在信仰上接受这个建议,我需要更好的解释。

任何人都能解释一下吗?

11 个答案:

答案 0 :(得分:71)

您无法知道引用是否无效:

除了通过如何使用引用之外,无法知道您的引用是否引用了有效内存。例如,如果您不确定何时删除内存,则不希望对堆上创建的内容使用引用。

您也永远无法知道您使用的指针是否指向有效内存。

你可以使用指针和引用进行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

答案 1 :(得分:5)

你做不到。你也不能用指针。考虑:



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或其他什么),但是没有什么可以一直工作。

如果你需要某种对象来确保指针不指向释放的内存,你需要比引用或标准指针更聪明的东西。

这就是为什么在处理指针和引用时需要非常小心所有权语义和生命周期管理。

答案 2 :(得分:4)

  

我的问题是我怎么知道在你初始化参考文献后没有释放/删除对象的内存。

首先,从不以任何方式检测内存位置是否已被释放/删除。这与它是否为空无关。指针也是如此。给定一个指针,您无法确保它指向有效的内存。您可以测试指针是否为null,但这就是全部。非空指针仍可指向释放的内存。或者它可能指向一些垃圾位置。

对于引用,同样适用于您无法确定它是否引用仍然有效的对象。但是,在C ++中没有“null引用”这样的东西,所以不需要检查引用是否为“null”。

当然,可以编写创建看起来像“空引用”的代码,并且该代码将被编译。但它不会正确。根据C ++语言标准,无法创建对null的引用。试图这样做是不明确的行为。

  

归结为我不能在信仰上接受这个建议,我需要更好的解释

更好的解释是:“引用指向有效对象,因为将其设置为指向有效对象”。你不必信仰它。您只需要查看创建引用的代码。它指向当时的有效对象,或者它没有。如果没有,则代码不正确,应该更改。

并且引用仍然有效,因为您知道它将被使用,因此您确保不会使它引用的对象无效。

真的那么简单。只要您不破坏它们指向的对象,引用就会保持有效。因此,在不再需要引用之前,不要销毁它指向的对象。

答案 3 :(得分:3)

在C ++中,引用主要用作函数的参数和返回类型。在参数的情况下,由于函数调用的性质,引用不能引用不再存在的对象(假设单线程程序)。在返回值的情况下,应该将自己限制为返回其生命周期长于函数调用的类成员变量,或者传递给函数的引用参数。

答案 4 :(得分:2)

您需要保持变量的健全性 - 即,如果您知道函数范围不会超过您的引用/指针,则只将参考/指针传递给某个函数。

如果您去释放一些句柄,然后尝试使用所述参考,您将阅读免费记忆。

答案 5 :(得分:1)

因为当你到达那里时,你肯定会做出一个未定义的行为。让我解释一下:))

说你有:

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,这是未定义的行为,如上例所示。所以,没有办法,现在必须有现实的方法,因为假设有效的参数是整个参考点。

答案 6 :(得分:1)

没有语法来检查引用是否有效。您可以测试指针为NULL,但没有有效/无效的参考测试。当然,引用的对象可以被一些错误的代码释放或覆盖。指针也是如此:如果非NULL指针指向已释放的对象,则无法对此进行测试。

答案 7 :(得分:1)

简短的是可以发生 - 但如果确实如此,那么你就会遇到严重的设计问题。你也没有真正的方法来检测它。答案是设计你的程序以防止它发生,而不是试图建立一些不会真正起作用的检查(因为它不能)。

答案 8 :(得分:1)

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别名)(编辑)

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
}

编辑:改变了我的例子,因为它被误解为测试参考的建议,而不是我想要展示的。我只是想证明无效的引用。

答案 9 :(得分:0)

我认为它“取决于”。我知道,这不是一个答案,但确实取决于它。我认为防守编码是一个很好的做法。现在,如果您的堆栈跟踪深度为10级,并且跟踪中的任何故障都会导致整个操作失败,那么请务必检查顶层并让任何异常升至顶部。但是,如果您可以从传递给您的空引用中恢复,则在适当的位置进行检查。根据我的经验,我必须将代码与其他公司一起集成在一起,检查(并记录)公共api级别的所有内容,这样可以避免在集成未按预期进行时发生的指责。

答案 10 :(得分:0)

我认为你可以从简单的并行性中受益:

  • T &T * const
  • 类似
  • T const &T const * const
  • 类似

引用与const非常相似,它们具有意义,因此有助于编写更清晰的代码,但不提供不同的运行时行为。

现在回答你的问题:是的,引用可能为null或无效。您可以测试空引用(T& t = ; if (&t == 0))但不应该发生&gt;&gt;根据合同,参考有效。

何时使用引用vs指针?在下列情况下使用指针:

  • 您希望能够更改指针
  • 您希望表达可能的无效

在任何其他情况下,请使用参考。

一些例子:

// 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,那里没有赢家;)