C++ references are not as safe as houses. They aren't really a safety feature at all, they are more of an optimisation. C++ references are in some ways even more dangerous than pointers, they cannot be tested or reset and at run-time they will be assumed to be valid and may even have been optimised away. This is offset by some compiler enforced grammar that prevents you from shooting yourself in the foot in some of the more obvious ways but it is only a token effort. At first sight C++ references may appear to offer a cushion of safety but it can easily be broken - and not just by obviously malicious or careless code. They are however, an excellent optimisation and, as long as you know you can trust them, a geat syntactical convenience.
The following is an account of areas in which I have dared to venture. It could be seen as a tutorial but it is more one persons attempt to clarify his own use so that doubts do not undermine confidence in his own code.
What is a C++ reference
At first sight it might seem that a C++ reference is just a syntactical sugar coating for a
const pointer but there is more to it than that.
A pointer, even a
const pointer, has a value that can be read and tested (it could be null) and an address at which that value is stored. A C++ reference does not. It is not an instruction to the compiler to create and hold an aliasing pointer, it is a direct instruction to make the alias. If the compiler can do some of this at compile time or optimise how it is done at run-time, it is free to do so. It is not obliged to hold an aliasing pointer. The C++ language does not give access to the aliasing pointer behind a C++ reference or its address, because it may not exist.
To summarise: When you declare a pointer, you are telling the compiler to create space to store a pointer. When you declare a C++ reference you are telling the compiler that that by this I mean that, fix it up for me! That is quite a big difference. With a C++ reference you are declaring your higher level intention and the compiler will optimise to that.
It is appropriate that C++ references don't use the
-> pointer dereference notation, there may well be no pointer, and it is entirely appropriate that operations are written as if they act on the object itself as that may well be what will happen at run-time - certainly you have instructed the compiler to get as close as it can to this.
In function parameter definitions
For many years, the only use of C++ references that I trusted was in the definitions of function parameters that will take a value.
void function(T& const t)
void function(T& t)
I know that they will be passed a value declared variable sitting in the same scope as the call. So that variable is guaranteed to be valid throughout the execution of the function. It is not possible to call it cleanly in any other way. There is a syntactical convenience over passing pointers, and a performance gain over passing by value;
t is not copied. Additionally the compiler is free to optimise away the reference as an entity in memory and its passage through the function call if it is able to. This is all win-win, Ï imagine that many programmers have now adopted this usage and are comfortable with it.
As a class member
The only long living class member context in which I feel totally secure using C++ references is as a back reference of a child to its parent:
and this is only because I am confident that the child will be represented in the parent either by value or a single ownership smart pointer and this will ensure there can be no child without a parent. This is also a case where the compiler is likely to optimise
m_Parent away completely as a run-time dereferencing intermediary. So although it may be the only use I can see for a C++ reference as a class member, it is a good one.
Referencing elements of a collection
Using C++ references in order to reference elements of a collection is potentially dangerous and remember, hitting an invalid C++ reference at run-time could be worse than hitting an invalid pointer. It can happen like this:
OK - that one is easy to see but if
T& t lives on through more code, it could easily be overlooked.
Despite this danger, I have recently ventured into this area because of the excellent optimisation it offered on a specific project. In this project I load up a set of pre-written tables. The key thing is that they are of fixed length and I do not add or remove any items, ever, though I may alter them. This is intrinsic to the design. Also intrinsic to the design is a need to load them from files very fast and to work intensely with their contents.
The first decision was to store the rows within the arrays that will hold the tables by value. The next was to pre-allocate the arrays to the known fixed size and to provide the elements with empty default constructors so that a block allocation of the array can take place without any construction code being executed. There is no point in initialising them because a block copy from the contents of a file will soon overwrite them all in one go.
I knew all the time that I would want to take an element of an array and do quite a bit of work with it and I don't want to dereference it out of the array every time I want to refer to it and I definitely don't want a copy out to happen every time I do that. So I will want to take an alias to an element of an array and work with that.
As a local variable
Now I know that no array elements are going to be deleted or moved while I am working on them and although the work I am doing may be complex, it all takes place within the scope of a single function. Reassured by this I, for the first time, declared a C++ reference as a local variable, like this:
apart from being nice to look at it allows maximum optimisation by the compiler, more so than:
which obliges the compiler to provide a specific location for storing
pElement whose address and value must be readable.
Either way there is the satisfaction of knowing that you are working on the array element itself rather than a copy of it .... but watch out with the syntax of the C++ reference. You only have to omit the ampersand and you have written:
which is completely different - you are making a copy and are working with that. This is much less efficient and changes will be lost..
Anxious to avoid this so easily made mistake, I took the measure of providing my
CElement classes with a dummy private copy constructor:
CElement(CElement & Element )
This prevents public copies from being made so that:
will not compile.
As a return type
In some cases I wanted to retrieve a reference to an element through special access functions, and had my first experience of declaring a C++ reference as a return type;
CElement& GetElement(int i)
of course many collections do provide such access functions that return a reference but we are more familiar with using them to make a copy
They also offer the choice of working directly through the reference
but it is advisable to scope the local reference declaration
CElement& Element to a short lifetime and it is essential to ensure that nothing moves in that collection while you are using it.
These have been my modest and cautious ventures into using C++ references. My overall impression is that C++ references provide more direct programming and potentially more direct execution but from a safety point of view C++ references will not look after you, it is you that must look after them. I am curious to hear the experience of others with them and how they view them.