References in C++



References in C++


When a variable is declared as reference, it becomes an alternative name for an existing variable. A variable can be declared as reference by putting ‘&’ in the declaration.
#include<iostream>
using namespace std;

int main()
{
  int x = 10;

  // ref is a reference to x.
  int& ref = x;

  // Value of x is now changed to 20
  ref = 20;
  cout << "x = " << x << endl ;

  // Value of x is now changed to 30
  x = 30;
  cout << "ref = " << ref << endl ;

  return 0;
}
Output:
 x = 20
ref = 30
Following is one more example that uses references to swap two variables.
#include<iostream>
using namespace std;

void swap (int& first, int& second)
{
    int temp = first;
    first = second;
    second = temp;
}

int main()
{
    int a = 2, b = 3;
    swap( a, b );
    cout << a << " " << b;
    return 0;
}
Output:
 3 2 


References vs Pointers
Both references and pointers can be used to change local variables of one function inside another function. Both of them can also be used to save copying of big objects when passed as arguments to functions or returned from functions, to get efficiency gain.
Despite above similarities, there are following differences between references and pointers.
A pointer can be declared as void but a reference can never be void
For example
int a = 10;
void* aa = &a;. //it is valid
void &ar = a; // it is not valid

References are less powerful than pointers
1) Once a reference is created, it cannot be later made to reference another object; it cannot be reseated. This is often done with pointers.
2) References cannot be NULL. Pointers are often made NULL to indicate that they are not pointing to any valid thing.
3) A reference must be initialized when declared. There is no such restriction with pointers
Due to the above limitations, references in C++ cannot be used for implementing data structures like Linked List, Tree, etc. In Java, references don’t have above restrictions, and can be used to implement all data structures. References being more powerful in Java, is the main reason Java doesn’t need pointers.
References are safer and easier to use:
1) Safer: Since references must be initialized, wild references like wild pointers are unlikely to exist. It is still possible to have references that don’t refer to a valid location (See questions 5 and 6 in the below exercise )
2) Easier to use: References don’t need dereferencing operator to access the value. They can be used like normal variables. ‘&’ operator is needed only at the time of declaration. Also, members of an object reference can be accessed with dot operator (‘.’), unlike pointers where arrow operator (->) is needed to access members.
Together with the above reasons, there are few places like copy constructor argument where pointer cannot be used. Reference must be used pass the argument in copy constructor. Similarly references must be used for overloading some operators like ++.


Can references refer to invalid location in C++?


In C++, Reference variables are safer than pointers because reference variables must be initialized and they cannot be changed to refer to something else once they are initialized. But there are exceptions where we can have invalid references.
1) Reference to value at uninitialized pointer.
  int *ptr;
  int &ref = *ptr;  // Reference to value at some random memory location
2) Reference to a local variable is returned.
int& fun()
{
   int a = 10;
   return a;
}
Once fun() returns, the space allocated to it on stack frame will be taken back. So the reference to a local variable will not be valid.

When do we pass arguments by reference or pointer?

In C++, variables are passed by reference due to following reasons:
1) To modify local variables of the caller function: A reference (or pointer) allows called function to modify a local variable of the caller function. For example, consider the following example program where fun() is able to modify local variable of main().
void fun(int &x) {
    x = 20;
}

int main() {
    int x = 10;
    fun(x);
    cout<<"New value of x is "<<x;
    return 0;
}
Output:
New value of x is 20

2) For passing large sized arguments:
 If an argument is large, passing by reference (or pointer) is more efficient because only an address is really passed, not the entire object. For example, let us consider the following Employee class and a function printEmpDetails() that prints Employee details.
class Employee {
private:
    string name;
    string desig;

    // More attributes and operations
};

void printEmpDetails(Employee emp) {
     cout<<emp.getName();
     cout<<emp.getDesig();

    // Print more attributes
}
The problem with above code is: every time printEmpDetails() is called, a new Employee abject is constructed that involves creating a copy of all data members. So a better implementation would be to pass Employee as a reference.
void printEmpDetails(const Employee &emp) {
     cout<<emp.getName();
     cout<<emp.getDesig();

    // Print more attributes 
}
This point is valid only for struct and class variables as we don’t get any efficiency advantage for basic types like int, char.. etc.

3) To avoid Object Slicing: 
If we pass an object of subclass to a function that expects an object of superclass then the passed object is sliced if it is pass by value. For example, consider the following program, it prints “This is Pet Class”.
#include <iostream>
#include<string>

using namespace std;

class Pet {
public:
    virtual string getDescription() const {
        return "This is Pet class";
    }
};

class Dog : public Pet {
public:
    virtual string getDescription() const {
        return "This is Dog class";
    }
};

void describe(Pet p) { // Slices the derived class object
    cout<<p.getDescription()<<endl;
}

int main() {
    Dog d;
    describe(d);
    return 0;
}
Output:
This is Pet Class
If we use pass by reference in the above program then it correctly prints “This is Dog Class”. See the following modified program.
#include <iostream>
#include<string>

using namespace std;

class Pet {
public:
    virtual string getDescription() const {
        return "This is Pet class";
    }
};

class Dog : public Pet {
public:
    virtual string getDescription() const {
        return "This is Dog class";
    }
};

void describe(const Pet &p) { // Doesn't slice the derived class object.
    cout<<p.getDescription()<<endl;
}

int main() {
    Dog d;
    describe(d);
    return 0;
}
Output:
This is Dog Class
This point is also not valid for basic data types like int, char, .. etc.

4) To achieve Run Time Polymorphism in a function 

We can make a function polymorphic by passing objects as reference (or pointer) to it. For example, in the following program, print() receives a reference to the base class object. print() calls the base class function show() if base class object is passed, and derived class function show() if derived class object is passed.
#include<iostream>
using namespace std;

class base {
public:
    virtual void show() {  // Note the virtual keyword here
        cout<<"In base \n";
    }
};


class derived: public base {
public:
    void show() {
        cout<<"In derived \n";
    }
};

// Since we pass b as reference, we achieve run time polymorphism here.
void print(base &b) {
    b.show();
}

int main(void) {
    base b;
    derived d;
    print(b);
    print(d);
    return 0;
}
Output:
In base
In derived

Exercise:
Predict the output of following programs. If there are compilation errors, then fix them.
Question 1
#include<iostream>
using namespace std;

int &fun()
{
    static int x = 10;
    return x;
}
int main()
{
    fun() = 30;
    cout << fun();
    return 0;
}


Question 2
#include<iostream>
using namespace std;

int fun(int &x)
{
    return x;
}
int main()
{
    cout << fun(10);
    return 0;
}


Question 3
#include<iostream>
using namespace std;

void swap(char * &str1, char * &str2)
{
  char *temp = str1;
  str1 = str2;
  str2 = temp;
}

int main()
{
  char *str1 = "GEEKS";
  char *str2 = "FOR GEEKS";
  swap(str1, str2);
  cout<<"str1 is "<<str1<<endl;
  cout<<"str2 is "<<str2<<endl;
  return 0;
}


Question 4
#include<iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}


Question 5
#include<iostream>
using namespace std;

int main()
{
   int *ptr = NULL;
   int &ref = *ptr;
   cout << ref;
}


Question 6
#include<iostream>
using namespace std;

int &fun()
{
    int x = 10;
    return x;
}
int main()
{
    fun() = 30;
    cout << fun();
    return 0;
}

Comments

Popular posts from this blog

Smart Pointers in C++ and How to Use Them

Operator Overloading in C++

How would you read in a string of unknown length without risking buffer overflow