Thursday, February 14, 2006
Reference Parameters

Topic

Classic Pass by Reference using Pointers
- You must do three things to pass a variable by reference using pointers in C or C++:
- 1. Define a formal parameter using a pointer:
         
void swap(int* x, int* y); 
- 2. Dereference the pointer parameter inside the function body:
         
void swap(int* x, int* y)
     {
        int temp;

        temp = *x;
        *x = *y;
        *y = temp;
     }  

  - 3. Send the address of the arguments in the function call
         
swap(&a, &b); 
 

Reference Parameters
- Reference parameters are a new option in C++ for pass-by-reference parameters.
- They have simpler syntax and are much easier to use. 
- They are extremely popular with C++ programmers.
- You only have to do one thing to pass by reference in C++:

- 1. Define a formal parameter using a reference:
         
void swap(int& x, int& y); 
- 2. Inside the function body the reference parameter is used just like any variable.  You do not have to de-reference:
         
void swap(int& x, int& y)
     {
        int temp;

        temp = x;
        x = y;
        y = temp;
     }  

  - 3. In the function call, simply write the arguments, without any additional operator:
         
swap(a, b); 
 

- Notice the ampersand, &, replaces the asterisk, *, in the prototype and function header.  The use of the ampersand here is not the same as the address-of operator.  The ampersand here is part of the data type, not an operator.

 
- A reference is also called an alias.  x in the function is just another name for the variable  a in the main function.  Any change made to x  is also made to  a  because they really are exactly the same variable.   
 

Problems with Pass by Value of Objects
- When a copy of an object is made in pass-by-value, a bit-wise copy is made.  No constructor is called.
- Any changes inside the function is made to the copy only.
- When the object disappears, the destructor is called.
-
When the original object disappears, the destructor is called again.  If the object contains pointers, which are copied, it is possible for the resources the pointers point to could be destructed twice.   
 

#include <iostream>

using namespace std;

class SomeClass
{
public:
    SomeClass();
    ~SomeClass();

    void setData(int d);
    int getData();

private:
    int data;
};


SomeClass::SomeClass()
{
    cout << "Constructor of SomeClass" << endl;
    data = 0;
}

SomeClass::~SomeClass()
{
    cout << "Destructor of SomeClass" << endl;
}

void SomeClass::setData(int d)
{
    data = d;
}

int SomeClass::getData()
{
    return data;
}

void changeTheObject(SomeClass object)
{
    object.setData(5);
}


int main()
{

    SomeClass object;

    cout << "Before call to changeTheObject: " << object.getData() <<
endl;

    changeTheObject(object);


    cout << "After call to changeTheObject: " << object.getData() <<
endl;


    return 0;
}
 

Output
 

Constructor of SomeClass
Before call to changeTheObject: 0
Destructor of SomeClass
After call to changeTheObject: 0
Destructor of SomeClass

             Notice that the Constructor is called once, but the Destructor is called twice.  The second object was created by a Copy Constructor written for you by the compiler.  More on Copy Constructors later in the course.
 

Solution: Design your object to always be consistent and pass by reference only
- When a copy of an object is made in pass-by-value, a bit-wise copy is made. 
- Any changes inside the function is made to the copy only.
- When the object disappears, the destructor is called.
-
When the original object disappears, the destructor is called again.  If the object contains pointers, which are copied, it is possible for the resources the pointers point to could be destructed twice.   
 

#include <iostream>

using namespace std;

class SomeClass
{
public:
    SomeClass();
    ~SomeClass();

    void setData(int d);
    int getData();

private:
    int data;
};


SomeClass::SomeClass()
{
    cout << "Constructor of SomeClass" << endl;
    data = 0;
}

SomeClass::~SomeClass()
{
    cout << "Destructor of SomeClass" << endl;
}

void SomeClass::setData(int d)
{
    data = d;
}

int SomeClass::getData()
{
    return data;
}
 

// The only difference is the ampersand
 //   in the formal parameter. 

void changeTheObject(SomeClass& object)
{
    object.setData(5);
}


int main()
{

    SomeClass object;

    cout << "Before call to changeTheObject: " << object.getData() <<
endl;

    changeTheObject(object);


    cout << "After call to changeTheObject: " << object.getData() <<
endl;


    return 0;
}
 

Output
 

Constructor of SomeClass
Before call to changeTheObject: 0
After call to changeTheObject: 5
Destructor of SomeClass

- Now the object is passed by reference.  Only one Destructor is called.  A well designed object and its data will always be consistent.
 

What if you don't want your object changed?
- Put the keyword const in front of the formal parameter.
- The
const causes a compiler error if any line of code inside the function attempts to change the object.
 

#include <iostream>

using namespace std;

class SomeClass
{
public:
    SomeClass();
    ~SomeClass();

    void setData(int d);
    int getData();

private:
    int data;
};


SomeClass::SomeClass()
{
    cout << "Constructor of SomeClass" << endl;
    data = 0;
}

SomeClass::~SomeClass()
{
    cout << "Destructor of SomeClass" << endl;
}

void SomeClass::setData(int d)
{
    data = d;
}

int SomeClass::getData()
{
    return data;
}
 

// The only difference is now is that const
 //   added to the reference parameter. 

void changeTheObject(const SomeClass& object)
{

    // The function call now tries to change a const
          //    object.  This causes a compiler error.

    object.setData(5);
}


int main()
{

    SomeClass object;

    cout << "Before call to changeTheObject: " << object.getData() <<
endl;

    changeTheObject(object);


    cout << "After call to changeTheObject: " << object.getData() <<
endl;


    return 0;
}
 

Output
 

Compiler error message. (Unfortunately it is very cryptic in g++ and does not clearly say what the problem is)

- Pass by reference using the ampersand, and constant pass by reference are the preferred ways to pass objects in C++.
- Pass by reference is much easier code when you get used to it.
 
 

Alternate Solution: Create a copy constructor
- When a copy of an object is made in pass-by-value, and a Copy Constructor has been written, that Copy Constructor will be called instead of making a bit-wise copy.
- Now you can properly control how the copy is created.
- Copy constructors have the form:

SomeClass(SomeClass& original);

- As before, the destructor will be called on the copy when the function returns, but if you created a well designed copy, the destructor will work as it should on that object only.
   
 

#include <iostream>

using namespace std;

class SomeClass
{
public:
    SomeClass();
    // Copy Constructor

    SomeClass(SomeClass&);
    ~SomeClass();

    void setData(int d);
    int getData();

private:
    int data;
};


SomeClass::SomeClass()
{
    cout << "Constructor of SomeClass" << endl;
    data = 0;
}

// Copy Constructor of SomeClass
SomeClass::SomeClass(SomeClass& original)
{
    cout << "Copy Constructor of SomeClass" << endl;
    data = original.data;
}


SomeClass::~SomeClass()
{
    cout << "Destructor of SomeClass" << endl;
}

void SomeClass::setData(int d)
{
    data = d;
}

int SomeClass::getData()
{
    return data;
}
 

// Now the copy constructor will be called. 
void changeTheObject(SomeClass object)
{
    object.setData(5);
}


int main()
{

    SomeClass object;

    cout << "Before call to changeTheObject: " << object.getData() <<
endl;

    changeTheObject(object);


    cout << "After call to changeTheObject: " << object.getData() <<
endl;


    return 0;
}
 

Output
 

Constructor of SomeClass
Before call to changeTheObject: 0
Copy Constructor of SomeClass
Destructor of SomeClass
After call to changeTheObject: 5
Destructor of SomeClass

- Notice that the Copy Constructor is now called when the object is passed by value.
 
 

Another Problem: Return values with dangling references
- You can return a reference from a function.  This is dangerous, because if the reference is to a locally created object, then that object will disappear when the function is called, but the reference will still be available.  The reference will refer to an object that does not exist. 
- This situation is called a dangling reference, and is similar to a pointer that does not point to anything.
 

#include <iostream>

using namespace std;

class SomeClass
{
public:
    SomeClass();
    ~SomeClass();

    void setData(int d);
    int getData();

private:
    int data;
};


SomeClass::SomeClass()
{
    cout << "Constructor of SomeClass" << endl;
    data = 0;
}

SomeClass::~SomeClass()
{
    cout << "Destructor of SomeClass" << endl;
}

void SomeClass::setData(int d)
{
    data = d;
}

int SomeClass::getData()
{
    return data;
}

SomeClass& createAnObject()
{
    SomeClass anObject;

    return anObject;
}


int main()
{
   Someclass& object = createAnObject();


   cout << "After call to createAnObject: " << object.getData() <<
endl;


    return 0;
}
 

Output
 

Constructor of SomeClass
Destructor of SomeClass
After call to createAnObject: -1073743568
 

    - Notice that the object is being by the returned reference after it has been destructed.  The result is a trash value instead of 0.
    - The moral: Never return by reference or by pointer anything that is local to the function.
 

Readings

Deitel and Deitel, Fifth Edition: 6.14,  Fourth Edition: Section 3.17.

 

Back to Csc 125 - Computer Science I, Programming in C++
  Scott Badman   Office: B132   Phone: 353-2250   sbadman@parkland.edu  

Parkland College, 2400 W. Bradley Avenue, Champaign, IL 61821