开发者

Why don't I need to check if references are invalid/null?

Reading http://www.cprogramming.com/tutorial/references.html, it says:

In general, references should always be valid because you must always initialize a reference. This means that barring some bizarre circumstances (see below), you can be certain that using a reference is just like using a plain old non-reference variable. You don't need to check to make sure that a reference isn't pointing to NULL, and you won't get bitten by an uninitialized reference that you forgot to allocate memory for.

My question is how do I know that the object's memory hasn't been freed/deleted AFTE开发者_运维技巧R you've initialized the reference.

What it comes down to is that I can't take this advice on faith and I need a better explanation.

Can anyone shed some light?


You can't know if references are invalid:

There is no way to know if your reference is referencing valid memory except by taking care of how you use references. For example you don't want to use a reference with something created on the heap if you are unsure when the memory will be deleted.

You also can never know whether the pointer you are using is pointing to valid memory or not as well.

You can do NULL checks with both pointers and references but typically you would never do a NULL check with a reference because no one would ever write code like this:

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

When to use a reference?

You probably want to use references in cases like this:

//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);

Shooting yourself in the foot is hard with references:

It's much harder to shoot yourself in the foot with references than it is with pointers.

For example:

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

*p = 3;//runtime error

You can't do this sort of thing with references since a reference must be initialized with it's value. And you can only initialize it with values that are in your scope.

You can still shoot yourself in the foot with references, but you have to REALLY try to do it.

For example:

int *p = new int;
*p = 3;
int &r = *p;
delete p;
r = 3;//runtime error


You can't. You also can't with a pointer. Consider:



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

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

Now, what code could you put in X::foo() to make sure that the i pointer is still valid?

Answer is that there is no standard check. There are some tricks that might work on msvc in debug mode (checking for 0xfeeefeee or whatever), but there's nothing that will consistently work.

If you need some sort of object that makes sure the pointer does not point at freed memory you'll need something much smarter than a reference or standard pointer.

This is why you need to be pretty darn careful with ownership semantics and lifetime management when working with pointers and references.


In C++, references are primarily intended to be used as the parameters and return types of functions. In the case of a parameter, a reference cannot refer to an object that no longer exists (assuming a single threaded program) because of the nature of a function call. In the case of a return value, one should restrict oneself to either returning class member variables whose lifetimes are longer than the function call, or reference parameters that are passed in to the function.


My question is how do I know that the object's memory hasn't been freed/deleted AFTER you've initialized the reference.

First, there is never any way to detect if a memory location has been freed/deleted. That has nothing to do with whether or not it is null. The same is true for a pointer. Given a pointer, you have no way to ensure that it points to valid memory. You can test whether a pointer is null or not, but that's all. A non-null pointer may still point to freed memory. Or it may point to some garbage location.

As for references, the same applies in that you have no way of determining whether it references an object that is still valid. However, there is no such thing as a "null reference" in C++, so there is no need to check if a reference "is null".

Of course, it is possible to write code that creates what looks like a "null reference", and that code will compile. But it won't be correct. According to the C++ language standard, references to null can not be created. Attempting to do so is undefined behavior.

What it comes down to is that I can't take this advice on faith and I need a better explanation

The better explanation is this: "a reference points to a valid object because you set it to point to a valid object". You don't have to take it on faith. You just have to look at the code where you created the reference. It either pointed to a valid object at that time, or it didn't. If it didn't, then the code is incorrect and should be changed.

And the reference is still valid because you know it is going to be used, so you have made sure not to invalidate the object it references.

It's really that simple. References stay valid as long as you don't destroy the object they point to. So don't destroy the object it points to until the reference is no longer needed.


You need to maintain sanity of your variables -- i.e., only pass a reference/pointer to some function if you know the function scope will not outlive you reference/pointer.

If you go and free some handle and then try to use said reference you will be reading free'd memory.


Because by the time you reach there you have made an undefined behavior for sure. Let me explain :)

Say you have:

void fun(int& n);

Now, if you pass something like:

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

But if you do the following:

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

By the time you reach fun, you will be dereferencing *n which is undefined behavior if the pointer is deleted as in the example above. So, there is no way, and there must be now way actually because assuming valid parameters is the whole point of references.


There is no syntax to check whether reference is valid. You can test pointer for NULL, but there is no valid/invalid test for a reference. Of course, referenced object can be released or overwritten by some buggy code. The same situation is for pointers: if non-NULL pointer points to released object, you cannot test this.


The short is that it could happen -- but if it does, you have a serious design problem. You also have no real way of detecting it. The answer is to design your program to prevent it from happening, not trying to build some sort of check that won't really work (because it can't).


I think you could benefit from a simple parallelism:

  • T & is similar to T * const
  • T const & is similar to T const * const

References are very similar to const in their intent, they carry a meaning and thus help write clearer code, but don't provide different runtime behavior.

Now to answer your question: yes it is possible that a reference be null or invalid. You can test for a null reference (T& t = ; if (&t == 0)) but it should not happen >> by contract a reference is valid.

When to use reference vs pointer ? Use a pointer if:

  • you wish to be able to change the pointee
  • you wish to express the possible nullity

In any other case, use a reference.

Some examples:

// 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

Passing arguments:

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

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

Attributes:

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;
};

Functionally references and pointers play the very same role. It's just a matter of contract. And unfortunately, both can invoke Undefined Behavior if you delete the object they refer to, there's no winner there ;)


C++ references are aliases. The effect of this is that dereferences to pointers don't necessarily happen where they appear, they happen where they are evaluated. Taking a reference to an object doesn't evaluate the object, it aliases it. Using the reference is what evaluates the object. C++ cannot guarantee references are valid; if it does, all C++ compilers are broken. The only way to do so is to eliminate all possiblity of dynamic allocation with references. In practice, the assumption is that a reference is a valid object. Since *NULL is undefined & invalid, it follows that for p = NULL, *p is also undefined. The problem with C++ is *p will be effectively passed to a function, or delayed in its evaluation until which time the reference is actually used. Arguing that it is undefined is not the point of the asker's question. If it were illegal, the compiler would enforce it, and so would the standard. Neither does, that I am aware of.

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) You can test a reference for aliasing a NULL pointer, &r is simply &(whatever r aliases to) (EDIT)

2) When passing a "dereferenced" pointer (*i) as a reference parameter, the dereference doesn't happen at the callsite, it may never happen, because it is a reference (references are aliases, not evaluations). That is the optimization of references. If they were evaluated at the callsite, either the compiler is inserting extra code, or it would be a call by value and less performant than a pointer.

Yes, the reference itself is not NULL, it is invalid, just as *NULL is invalid. It is the delaying of evaluation of dereference expressions that is not consistent with claiming it is impossible to have an invalid reference.

#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: Changed my example as it has been misconstrued as suggestion for testing references, not what I wanted to demonstrate. I only wanted to demonstrate the invalid reference.


I think it "depends". I know, that is not an answer, but it does really depend. I think coding defensively is a good practice to follow. Now if your stack track is 10 levels deep and any failure down the trace causes the entire operation to fail, then by all means, check at the top level and let any exceptions rise to the top. but if you can recover from someone passing you a null reference, the check where appropriate. In my experience, where I have to bring code together with other companies to integrate together, checking ( and logging ) everything at the public api level lets you deflect the finger pointing that happens when integration does not go as expected.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜