Wednesday, December 24, 2008

Are null references allowed in C++?

Before we answer this question, let us get back to some basics.

As you know, pointers is there both in C and C++ where as References is something that does not exist in C but in C++. It’s very common to see beginner level developers often getting confused with these two terminologies. There are a few who even argue references and pointers are one and the same. If you are one of those, let’s get this straight right now…

References and Pointers are two different concepts. Reference is an alias where as Pointer is a variable which holds the address of another variable. C++ compilers often implement references as pointers, but believe me they are not pointers and do not behave like pointers either.
Ideally, a Reference is another name for an existing object. Once a reference is initialized with an object, you can either use the object name or the reference name to refer to the object.

Consider the below example:

int aVar = 12;
int &aVarzAKA = aVar; // aVarzAKA is another name for aVar
--aVarzAKA; // aVar == 11
aVar = 10; // aVarzAKA == 10
int *aPtrVar = & aVarzAKA; // aPtrVar points to aVar

Now back to the question, “Are null references allowed in C++?

References can NOT be null where as a Pointer can be null (you know about Null Pointers, Don’t you? If not, we surely will talk about it later sometime.).

Never forget that all References need initialization and a Reference always refers to the object with which it was initialized. In the example quoted above, the reference aVarzAKA will refer to aVar for its entire lifetime.

Just because I say there are NO null references, you really need not believe me. You very well can question me what if I attempt to make a reference null?

Okay, let’s try and see what happens now…

Test &myTest = *static_cast(0); // Compiler Error

The above statement throws a compile time error. However, there are cases when the compiler may not detect if you create a null reference. During such less obvious attempts, even though the compiler is not complaining, it will cause undefined behavior at run-time. For example,

Test *startTest();
Test &myTest = * startTest(); // this probably is a bad code
if( &myTest == 0 ) // Results in undefined behavior

Observe the above code for a while. What if startTest() returns a null pointer?
Disaster! Yeah, the behavior of this code is undefined then. A better solution would be to use a pointer to hold the result of startTest as shown below:

Test *ptrTest = startTest();
if( ptrTest )
// Implementation goes here

Hence, the requirement that a reference must be initialized implies that the object to which it refers must be in existence when the reference is initialized. A reference is an alias for an object that already exists prior to the initialization of the reference. Once a reference is initialized to refer to a particular object, it cannot later be made to refer to a different object; a reference is bound to its initializer for its whole lifetime. In effect, after initialization a reference disappears and is simply another name for its initializer thereafter.


1. Initializing References to non-constants

A reference to a non-const cannot be initialized with a literal or temporary value. The below both snippets result in compilation error:
double &d = 12.3; // Compiler Error
someFunction( std::string("Hello"), std::string(", World") ); // Error

2. Initializing References to constants

In case of references to constants,

const double &d = 12.3; // Allowed
T sum( const T &a, const T &b ) {
return a + b;
const std::string &greeting
= sum(std::string("Hello"),std::string(", World")); // Allowed

When a reference to const is initialized with a literal, the reference is set to refer to a temporary location that is initialized with the literal. Therefore, d does not actually refer to the literal 12.3 but to a temporary location of type double that has been initialized with 12.3. Also, the reference greeting refers to the unnamed temporary string return value of the call to sum.
Remember that, such temporaries are destroyed (that is, go out of scope and have their destructors called) at the end of the expression in which they're created. However, when such a temporary is used to initialize a reference to const, the temporary will exist as long as the reference that refers to it.