2010-03-26 91 views
29

阅读http://www.cprogramming.com/tutorial/references.html,它说:为什么我不需要检查引用是否无效/空?

一般情况下,引用应该始终 是有效的,因为你必须始终 初始化一个参考。这意味着 ,除了一些奇怪的 的情况下(见下文),你可以是 确定使用参考只是 像使用普通的旧非参考 变量。您不需要检查到 请确保参考不是 指向NULL,并且您将不会得到 由未初始化的引用 咬住,您忘记分配内存 。

我的问题是我怎么知道对象的内存没有被释放/删除后,你已经初始化的引用。

归根结底,我不能对信仰采取这个建议,我需要更好的解释。

任何人都可以点亮一下吗?

+0

相关:http://stackoverflow.com/questions/1919608/checking-for-null-before-pointer-使用/ 1921403#1921403。 – 2010-03-26 16:50:07

+20

“我怎么知道对象的内存在你初始化引用后没有被释放/删除。”你怎么知道某人没有为你的类型“reinterpet_cast”一些完全不正确的对象,并将其作为参考传递给它?或者破坏了他们物体中的记忆,然后传给你?在C和C++中,你不必依靠你的调用者来做一个muppet。如果他们做了一些语言定义为无效的东西,比如解引用一个空指针或者删除一个对象,但是保留对它的引用,那么这就是他们的错误,他们可以调试它。 – 2010-03-26 16:52:50

回答

66

你可以不知道,如果引用是无效的:

有没有办法知道,如果你的参考引用有效的内存,除了通过采取你如何使用引用护理。例如,如果不确定何时删除内存,则不希望使用堆中创建的某个引用。

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

你可以做两个指针和引用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

非常好写。 +1。 – 2010-03-26 16:57:57

+6

“你可能不想使用一个在堆上创建的东西的引用”?为什么不?如果我在堆上创建一个实例,通过引用一个方法传递它然后释放它,我很好。唯一的问题是,如果方法存储该引用并假定它将保持有效。 – 2010-03-26 17:15:09

+0

@Steven Sudit:当然有一些场景,所以这就是我为什么使用可能。至少我可以在我自己的代码中说,如果我这样做,那么它是相对罕见的。 – 2010-03-26 17:20:08

2

你需要保持你的变量的理智 - 即,只是一个参考/指针传递给一些功能,如果你知道的功能范围不会活得比你引用/指针。

如果你去释放一些把手,然后尝试使用上述基准你会读free'd内存。

5

你不能。你也不能用指针。试想一下:



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或其他),但没有任何工作会一直工作。

如果你需要某种形式的对象,它可以确保指针不指向已释放的内存,你需要的东西比一个基准或标准指针更聪明。

这就是为什么你的指针和引用工作时必须相当不错小心所有权语义和生命周期管理。

+0

同样的问题摆在另一个回应:如果当我删除指针时,我将指针设置为null后检查为空?这不是我可以检查指针是否不再有效的方法吗? – jbu 2010-03-26 16:35:34

+5

您始终可以拥有别名指针。说'p'和'q'都是指向同一个内存的指针。你删除'p'并设置'p = NULL'。现在,'q'怎么办?这是无效的,也不是NULL。知道当你删除一个指针的所有别名时,并不总是微不足道的。 – 2010-03-26 16:37:47

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

+0

如果当我删除指针时,我将指针设置为null后检查null?这不是我可以检查指针是否不再有效的方法吗? – jbu 2010-03-26 16:35:10

+0

@jbu:没关系。如果你有一个对象的引用,并且它引用的对象消失了,你就有未定义的行为。更好的办法是实际解决问题,并确保你指的是不会从你下面掉下来的东西。 – GManNickG 2010-03-26 16:45:39

+1

@jbu:如果在删除之后你将指针设置为null,那么在删除之后,你*不能*引用指针来初始化一个引用。 – 2010-03-26 16:45:53

1

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

+0

你不能测试对NULL的引用吗?如果我做&myref == NULL怎么办? – codymanix 2010-03-26 16:38:02

+0

您可以测试null的参考。 – codenheim 2010-03-26 16:44:06

+0

@mrjoltcola:不,你不能。 @codymanix的建议是未定义的,因为空引用的存在是未定义的。 – jalf 2010-03-26 16:48:25

1

简而言之,它可能发生 - 但如果它发生,你有一个严重的设计问题。你也没有真正的方法来检测它。答案是设计你的程序来防止它发生,而不是试图建立某种不会真正起作用的检查(因为它不能)。

3

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

+0

除非单线程程序碰巧删除引用的地址,它知道它在堆上。一个人不会这样做,但它可能:) – 2010-03-26 16:43:24

+0

尼尔的答案是正确的。 – 2010-03-26 17:25:41

+0

“假设单线程程序”。嗯? 'void f(int&r,int * p){delete p; r + = 1; } int main(){int * a = new int(); f(* a,a); }'。创建一个悬而未决的参考。我没有看到程序的单线程或多线程与什么有关。即使只考虑自动化作为参考,一旦有人有参考,他们可以将其存储在一个对象中(通过构造函数),存储一个指向该对象的指针(全局),以便逃避该对象的范围,并通过它稍后作为函数参数。 UB当然是如此“不可能发生”,但与fn呼叫的性质无关。 – 2010-03-26 18:23:43

0

我认为它“依赖”。我知道,这不是一个答案,但它确实取决于。我认为防守编码是一种很好的做法。现在,如果您的堆栈轨道深度达到10级,并且任何跟踪失败导致整个操作失败,那么通过一切手段检查顶级,并让任何例外都升至顶级。但是如果您可以从通过您的某个空引用中恢复,则在适当的情况下进行检查。根据我的经验,在我必须将代码与其他公司集成在一起时,在公共api级别检查(和记录)所有内容可以让您在集成未按预期发生时发生偏移。

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是简单地&(无论ř别名)(编辑)

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 
} 

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

+0

你的“* i”是未定义的行为...也许它会起作用,也许它会后者崩溃,或者可能伸手背在背后,用旧袜子掐死你... – 2010-03-26 17:20:31

+0

未定义的行为意味着规范不会作出判断。关键是它是我知道的每个编译器中的合法行为,并发生在实际代码中,这些代码将指针与使用引用参数的库混合。 – codenheim 2010-03-26 17:26:44

+0

“某些答案”正在讨论标准定义的内容。某些实现要么保证UB特定情况下的某些行为,要么不保证它,但是当前做了可预测的事情,并不意味着答案是错误的。这只是描述一个接口和描述该接口的一个或多个特定实现的区别。 – 2010-03-26 18:33:03

4

我的问题是我怎么知道对象的内存没有被释放/删除后,你已经初始化的引用。

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

至于引用,这同样适用于你无法确定它是否引用仍然有效的对象。但是,在C++中没有这样的“空引用”,因此不需要检查引用是否为“null”。

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

什么它归结为是我不能接受的信仰这个建议,我需要一个更好的解释

更好的解释是这样的:“一个参考点,以一个有效的对象,因为将它设置为指向一个有效的对象“。你不必信赖它。你只需要看看你创建引用的代码。它当时指向一个有效的对象,或者它没有。如果没有,那么代码是不正确的,应该改变。

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

这真的很简单。只要您不摧毁它们指向的对象,引用就会保持有效。所以不要摧毁它指向的对象,直到不再需要引用为止。

+0

@jalf:在我看来,忽略基于“未定义”的功能并不能帮助提问者。我们都知道“有趣(\ * i)”会发生。任何带参考参数的函数都可能接收到一个“空引用”,直到在被调用者中评估才会被找到。这是不同的。是的,你应该在通话之前检查指针,我同意。但是,如果C++ _really_保证了状态,那么在函数调用之前,必须检查表达式“* i”处的指针取消引用。请将我指向涵盖此的标准。 – codenheim 2010-03-26 17:19:21

+2

@mrjoltcola:当C++“保证”任何事情时,任何事情都是如此,该保证取决于程序的格式良好并且不会引发未定义的行为。如果C++没有“真的保证”引用是非空的,那么它也不会“确保”1 + 1 == 2。一旦未定义的行为被调用,其中一方或双方都可能失败。 1.3.12表示“完全忽略不可预知的结果”是允许的。 – 2010-03-26 18:11:43

+0

你说服了我,但那些说“不可能发生”的答案是错误的,这就是促使我回答的原因。 “不可能发生”是错误的选择。我想每个人都误解了我的观点。 – codenheim 2010-03-26 18:37:22

0

我想你可以从一个简单的并行机制:

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

的参考资料,他们的意图非常相似const,他们携带一个含义,从而帮助编写更清晰的代码,但不提供不同的运行时行为。

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

何时使用引用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; 
}; 

功能引用和指针起到同样的作用。这只是一个合同问题。不幸的是,如果你删除了它们引用的对象,它们都可以调用未定义的行为,这里没有赢家;)