Advanced
References and Pointers pass by reference for efficiency. Each time you pass an object into a function
by value, a copy is made of it. Each time you return an object from a function by value, another copy is made. This
will cause you to use more memory than you need to and your programs will run slower. The size of a user-created
object on the stack is the sum of its member variables, and these in turn can be user-created. Passing such a massive structure by copying it onto the stack is expensive in memory and performance.
This is because its copy constructor is called so many times.
Passing objects by reference saves unnecessary calls to the copy
constructor.
"Not
only does God play dice, but... he sometimes throws
them where they cannot be seen."
- Stephen W. Hawking
copy constructor - called each time a temporary copy of an object is put on the stack. copy destructor - if an object is returned by value, a copy of the object must be made and destroyed as well.
The copy constructor looks like the constructor - it has the
same name as the class. It is a reference or alias to the
class. Note that, in large objects constructor and destructor calls are expensive in speed and memory. Example:
//Declare
(prototype) 2
functions with a return type of Monster
Monster PassMonsterByValue(Monster theMonster);
Monster *PassMonsterByPointer(Monster
*theMonster);
The return value from
PassMonsterByValue() is not assigned to any object, so the temporary object created for the return
object of the function is thrown away (discarded), calling the destructor and producing
the 7th line of output. This is because we did not
have any object to catch the object returned by invoking the
function. Because PassMonsterByValue() has ended its local copy goes out of scope and is destroyed producing (8th line of output.
Program execution returns to the main() function.
PassMonsterByPointer is called, producing 9th line of output. The parameter here, however, is passed by reference this time,
rather than value. No copy is produced so there is no output.
PassMonsterByPointer() prints a message that produces
the 10th line of output. It also returns the
Monster object by reference, so there are no calls to the constructor or destructor.
The program ends and the Monster object, KingKong, goes out of scope producing one
last, final call to the destructor and producing 11th line of output.
By contrast, PassMonsterByValue() produces two calls to the copy constructor and
two calls to the destructor because it passes the Monster by value. FuncitonTwo() produces no calls because it passes the
Monster by reference. Passing a pointer to
PassMonsterByPointer()
is more efficient, yet it can be dangerous.
PassMonsterByPointer() is not allowed to change the
Monster object passed to it, yet it is given the address of the
Monster. This exposes the object to possible change and defeats the protection offered in passing by value.
We can combine the protection of passing by value, where a copy
of the object is altered rather than the original, and the
efficiency of passing by reference (not creating extra copies
and consuming more memory) if we use constant pointers.
Passing a constant pointer prevents calling any non-constant method on
Monster and protects the object from change. Example:
// Passing pointers to objects
#include <iostream.h>
//Prototype
- return a const pointer to a const Monster and also
take one as an argument
const Monster
*const PassMonsterByPointer(const
Monster *const theMonster);
//Must use address
of operator when passing object since function uses
a pointer
//At this point, we don't have to catch the return
value since it is just an alias of what is passed in
PassMonsterByPointer(&JollyGreenGiant);
cout << "JollyGreenGiant is of strength ";
cout << JollyGreenGiant.GetStrength() << ".\n";
//PassMonsterByPointer, passes
and returns a const pointer so its members can not
be changed
const Monster
*const PassMonsterByPointer(const
Monster *const theMonster)
{
cout << "Function Two. Returning...\n";
cout << "JollyGreenGiant is now of strength " << theMonster->GetStrength();
cout << ".";
//theMonster->SetStrength(8);
//Could not do this - members are constant and can't
change!
return theMonster;
}
Output:
Making a monster...
Monster Constructor...
JollyGreenGiant is of strength 200.
JollyGreenGiant is of strength 500.
Calling PassMonsterByPointer...
Function Two. Returning...
JollyGreenGiant is now of strength
500.JollyGreenGiant is of strength 500.
Monster Destructor...
In this case, Monster has
added two accessor functions: the const function
GetStrength(), and the NOT const function SetStrength(). The member variable itsStrength is private, and so must use
these accessor methods.
The copy constructor is never called since the object is passed by reference,
not value, and therfore no copies are made.
PassMonsterByValue() is not used, PassMonsterByPointer() is called. The parameter and return value are now declared to take a const pointer to a const object and to return a const pointer to a const object.
Again, parameters and return values are passed by reference, so no copies are made and
the copy constructor is not called. The pointer in
PassMonsterByPointer() is now const, so
it can not call the non const method SetStrength(). Even
though the method itself is not constant, the pointer is
constant and the
Monster object is constant, making, for all intents and
purposes, all of the class's data members and
methods constant. If the call to SetStrength() were not commented out
inside PassMonsterByPointer(), the program would not compile. Objects
created in main() are not const, so JollyGreenGiant can call
the SetStrength() method. The address of this non const object is passed to
PassMonsterByPointer(), but because FuntionTwo()'s declaration declares the pointer to be a const pointer the
a constant object, the non-constant method would be treated as if it were constant.
This saves a few calls to the constructor and destructor and so is more
efficient, and it prevents changing the object passed in, but it is still cumbersome because the objects passed to the function
are pointers. Is there a better way?
References may be used as an alternative. The object would be easier to work with if a reference were passed in, rather than a pointer, since you know the object
in this case will never be null. Example:
//PassMonsterByReference passes a const reference to a const object
const Monster
&PassMonsterByReference (const
Monster &theMonster)
{
cout << "Function Two. Returning...\n";
cout << "Mothra is now of strength " <<
theMonster.GetStrength();
cout << ".";
//theMonster.SetStrength(8);
Could not do this - members are constant!
return theMonster;
}
Output:
Making a Monster...
Monster Constructor...
Mothra is of strength 300.
Mothra is of strength 700.
Calling PassMonsterByReference...
Function Two. Returning...
Mothra is now of strength 700.Mothra is of strength
700.Monster Destructor...
The output is identical to the previous program. The
significant change is that PassMonsterByReference() now takes and returns a
const reference to a const object. Working with references is simpler than working with pointers, yet the same efficiency
of not passing by value and safety of const are obtained.
When do we chose to use references and when do we choose to use pointers?
Many C++ programmers strongly prefer references over pointers. References are cleaner and do
a better job at hiding information, data members and methods.
In this way they are more true to OOP theory - data hiding and
encapsulation. Remember that references can not be reassigned, however. If you need to point first to one object, and then to another, use a pointer.
Again, if you are going to reassign, you can not use references.
Also, references can not be NULL. If there is any chance that the object in question could be null, use
must a pointer.
One example concerns the use of "new". If "new" cannot allocate memory on the free store, it returns a null pointer. Since a reference cannot be null, you must not initialize a reference to this memory until you've checked that it is not null.
Though null is preferable for pointer conditions, it is not for
references. Example:
int *IntegerPointer = new int;
if(IntegerPointer != NULL)
//must check to be
sure pointer is not null before assigning to reference int
&IntegerReference = *IntegerPointer;
A pointer to an int, pInt is declared and initialized with memory returned by operator
"new". The address of pInt is tested, and if it is not null, pInt is
dereferenced. Dereferencing an int variable creates an int object, and
the reference rInt is initialized to refer to that object.
The reference rInt then becomes an alias for the int on the heap
created with the operator "new".
Don't return a reference to an object that isn't in scope - don't overdo
referencing. We must ask, "What is the object I am aliasing, and will it still exist every time it is used?".
Here is an example of the danger of returning a reference to an object that no longer exists:
Output:
Compile error - Attempting to return a reference to a local object!
The body of GoingNowhere() declares an object local to the function
itself, but nto global, of type Employee, and it initializes its age and
SSN through the constructor. It then returns that local object by reference.
The problem is, when a functoin ends all its data members are
destroyed to free up memory. Its objects do not persist. Smart compilers
will flag this as an error. When GoingNowhere() returns the local object,
DrewBarrymore will be destroyed! The reference returned by this function will be an alias to a non-existent object,
NULL.
You might be tempted to solve this by having
GoingNowhere() create
DrewBarrymore on the heap, so that when you return from
GoingNowhere(),
DrewBarrymore will still exist. This is also a problem. What do you do with the memory you have allocated for
DrewBarrymore when you are done with it? Example:
//This
function creates an employee object on the heap
aliased by a reference
Employee &MemoryLeak()
{
//Since
CharlesGermany is a pointer, using it without
dereferencing
//it will
render a memory address rather than a value!
Employee
*CharlesGermany =
new Employee(34,250);
cout << "Address of CharlesGermany: " << CharlesGermany << endl;
return
*CharlesGermany;
}
Output:
Address of CharlesGermany: 0x2bf4
RefToEmployee is 34 years old!
Address of
&RefToEmployee: 0x2bf4
This will compile, link and appear to work. But it is a ticking
time bomb! Ahhhhhhhhhhhh! A memory leak!
MemoryLeak() has been changed so that it no longer returns a reference to a local variable. Memory is allocated on the free store
(that is, the heap), and is assigned to a pointer. The address that
that pointer holds is printed,
since it is used without dereferencing, and then the pointer is dereferenced so the Employee object can be returned by reference
via the value pointed to by the pointer. Remember that this object is on the heap,
not local to the function, so it does not go out of scope this
time when the function ends.
When MemoryLeak() returns its value, it ends and its reference
to the Employee object "CharlesGermany", on the heap, is
returned via the pointer to a reference to an Employee object in
main(). That object is then used to obtain the Employee's
age, which is then output.
To prove that the reference declared in main() is referring to the object put on the free store(heap) in
MemoryLeak(), the
"address of" operator is applied to the reference RefToEmployee,
revealing it has the same address as the object
"CharlesGermany", on the heap. It is an alias for this
object. The reference displays the address of the object it refers to,
and this matches the address of the object on the heap.
How then, can the memory be freed? You can't call delete on the reference. You could create another pointer and initialize it with the address obtained from
RefToEmployee. This deletes the memory and plugs the memory leak, but what then is
RefToEmployee referring to after delete is called on the pointer? A reference must always alias an actual object, if it references a null object the program is invalid.
Remember that a reference should NEVER be NULL. There are 3
possible solutions:
1) Declare an Employee object and return the Employee from MemoryLeak()
by value rather than reference.
2) Declare the Employee object on the heap in MemoryLeak(), but have
MemoryLeak() return a pointer to that memory instead
of a reference. Then the calling function can delete the pointer when it is done.
3) Declare the object in the calling function, main(), and pass it to
MemoryLeak() by reference.
When a program allocates memory on the free store, a pointer is returned. It is imperative that you keep a pointer to that memory, because once the pointer is lost, the memory cannot be deleted and
it becomes a memory leak. Eeeek! A leak!
:) As you pass this block of memory between functions, someone will "own" the pointer. Typically the value in the block will be passed using references, and the function that creates the memory is the one that deletes it.
However, this is not a rule that is set in stone.
It can be dangerous for one function to create memory and another to free it. Ambiguity about who owns the pointer can lead to either forgetting to delete a pointer
at all, or deleting a pointer twice. Both of these actions
can cause serious problems in a program. For safety, build your functions to delete the memory they create.
Make them responsible parents.
If you are writing a function that needs to create memory and then
has to pass it back to the calling function, change your interface.
Instead, have the calling function allocate the memory, and then pass it into your function by reference.
The function can then manipulate the arguments and parameters
and then pass it back to its parent method. This moves all of the memory management out of your program, and back to the function that is prepared to delete it.
Remember:
1 - Pass parameters by value when you must, but by reference when you
can.
2 - Return by value when you must.
3 - DON'T pass by reference if the item referred to may go out of scope.
4 - DON'T use references to null objects.