Why Is a C++ Reference Considered Safer Than a Pointer

Why is a c++ reference considered safer than a pointer?

It's considered safer because a lot of people have "heard" that it's safer and then told others, who now have also "heard" that it's safer.

Not a single person who understands references will tell you that they're any safer than pointers, they have the same flaws and potential to become invalid.

e.g.

#include <vector>

int main(void)
{
std::vector<int> v;
v.resize(1);

int& r = v[0];
r = 5; // ok, reference is valid

v.resize(1000);
r = 6; // BOOM!;

return 0;
}

EDIT: Since there seems to be some confusion about whether a reference is an alias for an object or bound to a memory location, here's the paragraph from the standard (draft 3225, section [basic.life]) which clearly states that a reference binds to storage and can outlive the object which existed when the reference was created:

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or
released, a new object is created at the storage location which the original object occupied, a pointer that
pointed to the original object, a reference that referred to the original object, or the name of the original
object will automatically refer to the new object and, once the lifetime of the new object has started, can
be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied,
    and
  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static
    data member whose type is const-qualified or a reference type, and
  • the original object was a most derived object of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).

Why is the argument of the copy constructor a reference rather than a pointer?

There are many reasons:

  1. References cannot be NULL. OK, it's possible to create a NULL reference, but it's also possible to cast a std::vector<int>* into a std::vector<SomeType>*. That doesn't mean such a cast has defined behavior. And neither does creating a NULL reference. Pointers have defined behavior when set to NULL; references do not. References are therefore always expected to refer to actual objects.

  2. Variables and temporaries cannot be implicitly converted into pointers to their types. For obvious reasons. We don't want pointers to temporaries running around, which is why the standard expressly forbids doing it (at least when the compiler can tell you are doing it). But we are allowed to have references to them; these are implicitly created.

  3. Because of point number 2, using pointers rather than references would require every copy operation to use the address-of operator (&). Oh wait, the C++ committee foolishly allowed that to be overloaded. So any copy operation would need to actually use std::addressof, a C++11 feature, to get the address. So every copy would need to look like Type t{std::addressof(v)}; Or you could just use references.

What is the difference between a C# Reference and a Pointer?

C# references can, and will be relocated by garbage collector but normal pointers are static. This is why we use fixed keyword when acquiring a pointer to an array element, to prevent it from getting moved.

EDIT: Conceptually, yes. They are more or less the same.

What's the low-level difference between a pointer an a reference?

Theoretically, they could be implemented in different ways.

In practice, every compiler I've seen compiles pointers and references to the same machine code. The distinction is entirely at the language level.

But, like cdiggins says, you shouldn't depend on that generalization until you've verified it's true for your compiler and platform.

Is the practice of returning a C++ reference variable evil?

In general, returning a reference is perfectly normal and happens all the time.

If you mean:

int& getInt() {
int i;
return i; // DON'T DO THIS.
}

That is all sorts of evil. The stack-allocated i will go away and you are referring to nothing. This is also evil:

int& getInt() {
int* i = new int;
return *i; // DON'T DO THIS.
}

Because now the client has to eventually do the strange:

int& myInt = getInt(); // note the &, we cannot lose this reference!
delete &myInt; // must delete...totally weird and evil

int oops = getInt();
delete &oops; // undefined behavior, we're wrongly deleting a copy, not the original

Note that rvalue references are still just references, so all the evil applications remain the same.

If you want to allocate something that lives beyond the scope of the function, use a smart pointer (or in general, a container):

std::unique_ptr<int> getInt() {
return std::make_unique<int>(0);
}

And now the client stores a smart pointer:

std::unique_ptr<int> x = getInt();

References are also okay for accessing things where you know the lifetime is being kept open on a higher-level, e.g.:

struct immutableint {
immutableint(int i) : i_(i) {}

const int& get() const { return i_; }
private:
int i_;
};

Here we know it's okay to return a reference to i_ because whatever is calling us manages the lifetime of the class instance, so i_ will live at least that long.

And of course, there's nothing wrong with just:

int getInt() {
return 0;
}

If the lifetime should be left up to the caller, and you're just computing the value.

Summary: it's okay to return a reference if the lifetime of the object won't end after the call.

Double pointer vs pass by reference pointer

"Why using reference to pointer instead of pointer to pointer"? You will get the same answer as if asking "why using pointer instead of reference" for any other kind of variable...

Basically:

  • references (to pointer or any other variable) are smart because there should always be an object behind

  • pointers (to pointer or any other variable) are smart because they could possibly be NULL (optional)

  • references (to pointer or any other variable) are not available in C

  • references (to pointer or any other variable) are smart because they can be used as objects (no need to dereference like pointers, easier syntax, rading)

  • etc...

There are many posts answering this question already:

What are the differences between a pointer variable and a reference variable in C++?

Are there benefits of passing by pointer over passing by reference in C++?

When Exactly Is A C#/.NET REF Parameter dereferenced? (Looking At Thread Safety)

I wrote this little sample program to demonstrate what I mean about ref being dereferenced every time it is used. I hope this helps.

As far as thread safety is concerned, when ref gets dereferenced has little effect. Your code needs to ensure that no two threads are modifying the value at the same time, and that no threads are reading the value until the writing threads have completed.

Your new code is much improved. But remember, it is the locks that are making your code thread safe, not when ref is dereferenced.

class Program
{
static void Main(string[] args)
{
var a = new Test { Name = "First" };

ref Test b = ref a;
ref Test c = ref a;

Console.WriteLine(b.Name); // dereferences b, prints "Hello World!"
Console.WriteLine(c.Name); // dereferences c, prints "Hello World!"

b = new Test { Name = "Goodbye :(" }; // change the target of ref b to a new object

// dereference c again, points to the new object
// prints "Goodbye :("
Console.Write(c.Name);
}
}

public class Test
{
public string Name { get; set; }
}

Why is it allowed to cast a pointer to a reference?

Well, that's the purpose of reinterpret_cast! As the name suggests, the purpose of that cast is to reinterpret a memory region as a value of another type. For this reason, using reinterpret_cast you can always cast an lvalue of one type to a reference of another type.

This is described in 5.2.10/10 of the language specification. It also says there that reinterpret_cast<T&>(x) is the same thing as *reinterpret_cast<T*>(&x).

The fact that you are casting a pointer in this case is totally and completely unimportant. No, the pointer does not get automatically dereferenced (taking into account the *reinterpret_cast<T*>(&x) interpretation, one might even say that the opposite is true: the address of that pointer is automatically taken). The pointer in this case serves as just "some variable that occupies some region in memory". The type of that variable makes no difference whatsoever. It can be a double, a pointer, an int or any other lvalue. The variable is simply treated as memory region that you reinterpret as another type.

As for the C-style cast - it just gets interpreted as reinterpret_cast in this context, so the above immediately applies to it.

In your second example you attached reference c to the memory occupied by pointer variable pc. When you did c = 'B', you forcefully wrote the value 'B' into that memory, thus completely destroying the original pointer value (by overwriting one byte of that value). Now the destroyed pointer points to some unpredictable location. Later you tried to dereference that destroyed pointer. What happens in such case is a matter of pure luck. The program might crash, since the pointer is generally non-defererencable. Or you might get lucky and make your pointer to point to some unpredictable yet valid location. In that case you program will output something. No one knows what it will output and there's no meaning in it whatsoever.

One can rewrite your second program into an equivalent program without references

int main(){
char* pc = new char('A');
char* c = (char *) &pc;
std::cout << *pc << "\n";
*c = 'B';
std::cout << *pc << "\n";
}

From the practical point of view, on a little-endian platform your code would overwrite the least-significant byte of the pointer. Such a modification will not make the pointer to point too far away from its original location. So, the code is more likely to print something instead of crashing. On a big-endian platform your code would destroy the most-significant byte of the pointer, thus throwing it wildly to point to a totally different location, thus making your program more likely to crash.



Related Topics



Leave a reply



Submit