Search for Courses, Topics

Pointers vs References in C++

Learn about Pointers vs References in C++.

19 Jul 2021-13 mins read
quiz
Challenge Inside! : Find out where you stand! Try quiz, solve problems & win rewards!

Overview

A reference in C++ is an alias for another variable. There are multiple types of references such as lvalue references, rvalue references, and constant references. References can also be used as the return type of functions or in the parameter type of functions. Although references are safer and easier to use, they are less powerful than pointers.

Scope

  • This article defines what a reference variable in C++ stands for and how it is used.
  • This article also highlights the differences between a reference and a pointer in C++.

Introduction

In the real world, we use many synonymous words interchangeably. One might refer to a "car" as an "automobile" or use the word "residence" instead of "house". Similarly, a reference variable in C++ is just an alternative name for another variable of the same type. After the declaration of the reference, the previous and the new variable name can be used interchangeably and they would access/modify the same data and the same location in memory.

A pointer to a variable in C++ points to the memory location where the variable is stored. A reference in C++ is not a "new" variable and thus, a separate memory location is not allocated for it. Since it can access the same memory location** as the variable of which it is a reference, a reference variable is internally implemented as a pointer. But the reference is not the same as pointer there are many differences between them which we will see in this article.

Creating Reference in C++

We use the reference operator in C++ (& operator) to create a reference. The general syntax for creating a reference is as follows:

<data-type> &<reference-variable-name> = <existing variable of same type>

Note: In the syntax above, the data type of the variable on the right side of the assignment operator should be the same as that of the reference variable. The following code shows how a reference can be created for an integer type variable. The same code can be extended to other data types as well.

#include <iostream>

using namespace std;

int main()
{
    int x = 10;
    cout << "X = "<< x << endl;
    int &y = x;    // y is a reference to x
    y = 20;        // This will change the value of x 
    cout << "X = "<< x;

    return 0;
}

Output:

X = 10
X = 20

In the output shown above, we can see that the initial value of x is 10. Then we declare y as a reference to x and use y to change the value of x. Using the second cout statement, we can verify that the value of x was indeed changed by y.

Types of References

References in C++ can be categorized based on the type of variables they can refer to such as lvalue references and rvalue references. Additionally, we can also have constant references in C++ which provides immutability. Let's explore these types in further detail.

Lvalue References

Lvalue references in C++ are used to reference existing objects (and not temporary objects and literals). The syntax for declaring an lvalue reference is:

<data-type> &<variable-name> = <existing-object-name>

Lvalue references are mostly used to create an alias for another variable. They are also used when an object has to be passed by reference to a function.

As per the syntax shown above, the right-hand side of the expression mentions an existing object name. In other words, we need to provide an lvalue on the right-hand side of the expression. Thus, we can say that an lvalue reference can only refer to a variable existing in memory.

If we want to store the reference of a rvalue (such as temporary objects and literals) in a lvalue reference, we need to use the const keyword as follows:

const <data-type> &<variable-name> = <rvalue>

The following code demonstrates the usage of lvalue references in C++ :

#include <iostream>

using namespace std;

int main()
{
    int x = 10; 
    int &lref1 = x;  // This is OK since it x is a lvalue
    // int &lref2 = 10; -> This is not OK since 10 is rvalue and lref2 is non-const
    const int &lref3 = 10;  // This is also OK since 10 is a rvalue and lref3 is constant
    lref1 = 40;
    // lref3 = 40; -> This is not OK since lref3 is constant

    return 0;
}

In the above code, if we uncomment line 9, we'll get the following error because lref2 is not a constant variable and we are trying to initialize it with a rvalue.

main.cpp:9:18: error: cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int     int &lref2 = 10;  //This is not OK since 10 is rvalue and lref2 is non-const
                  ^~

Similarly, if we uncomment line 12, we'll get the following error because lref3 is a constant variable.

main.cpp:12:13: error: assignment of read-only reference ‘lref3’
     lref3 = 40; // This is not OK since lref3 is constant
             ^~

Rvalue References

Rvalue references in C++ are used to reference temporary objects and literal. The benefit of using rvalue references is that we can utilize the resources of temporary objects and prolong their life in the program. The syntax for declaring a rvalue reference is :

<data-type> &&<variable-name> = <rvalue>

While using rvalue references in C++, we can even modify the rvalue that is stored in the reference variable (which is not possible using a constant lvalue reference). The following code illustrates this:

#include <iostream>

using namespace std;

int main()
{
    const int& lref = 10;
    int &&rref = 10;
    // lref = 20; -> This is not allowed because lref is constant
    rref = 20;  // This is allowed 
    
    return 0;
}

Constant References

Constant references in C++ are used in function arguments when we want to prevent accidental changes to the value of the parameter. A constant reference in C++ behaves exactly as a constant variable would doesn't allow modifications to its value. Both lvalue and rvalue references can be made constant as shown in the code below:


#include <iostream>

using namespace std;

void func1(const int &a)
{
    a = 10;
}

void func2(const int &&b)
{
    b = 10;
}

int main()
{
    int x = 5;
    func1(x);
    func2(5);

    return 0;
}

Output:

main.cpp:8:9: error: assignment of read-only reference ‘a’
     a = 10;
         ^~
main.cpp: In function ‘void func2(const int&&)’:
main.cpp:13:9: error: assignment of read-only reference ‘b’
     b = 20;
         ^~

Explanation:

The output above shows that we cannot modify the constant references a and b inside func1() and func2() respectively. The phrase "read-only" here means that these variables cannot be modified and only their data can be accessed.

Properties of References

  1. References in C++ must be initialized at the time of their declaration. This means that the following snippet is not valid.

    int &ref;    // This is not allowed
    int x = 10;
    ref = x;
    
  2. After a variable has been initialized as a reference, it cannot be reassigned to refer to another variable. The following snippet shows this.

    int x = 10, y = 20;
    int &ref = x;
    &ref = y;    // This is not allowed
    
  3. Function parameters can be passed as references. This is especially helpful when we want to pass large objects as parameters. So, we can reduce the overhead of copying the large object into the parameters.

    It is also useful when we want the changes made to the parameters in the function to be reflected in the variables in the calling function.

    The following code shows the usage of references as function parameters :

    #include <iostream>
    
    using namespace std;
    
    struct Node {
        int x, y;
    };
    
    void swapValue(Node &param) {
        swap(param.x, param.y);
    }
    
    int main()
    {
        Node obj;
        obj.x = 10;
        obj.y = 20;
        cout<<" Value of obj before function call = "<< obj.x<<" and "<<obj.y<<endl;
        swapValue(obj);
        cout<<" Value of obj after function call = "<< obj.x<<" and "<<obj.y;
        return 0;
    }
    

Output:

    Value of obj before function call = 10 and 20
    Value of obj after function call = 20 and 10

Explanation:

As we can see in the above code, passing obj as a reference saves us the overhead of copying two variables x and y. Also, the swap in swapValue() method is reflected in the main() method as well as evident from the output.

  1. In case of nested data structures, we can use references as a shortcut to access the innermost data members. The following shows how this is done :
    #include <iostream>

    using namespace std;

    struct A {
        int x;
    };

    struct B {
        A obj_a;
    };

    struct C {
        B obj_b;    
    };

    int main()
    {
        C obj_c;
        int &y = obj_c.obj_b.obj_a.x;
        y = 20;
        cout<<obj_c.obj_b.obj_a.x;
        return 0;
    }

Output:

    20

Explanation:

In the above code, we can now use y as an alias for obj_c.obj_b.obj_a.x which makes the program more readable and clean.

  1. While using for each loops, we must use references if we want to modify the objects being accessed. The following code demonstrates this :
    #include <iostream>
    #include <vector>

    using namespace std;

    int main()
    {
        vector<int> vect{1, 2, 3, 4, 5};

        cout<<"Without reference : \n";

        for (int x : vect)  // This won't change the elements in the vector
            x = x + 5;      // x is a copy of the elements in the vector

        for (int x : vect) 
            cout << x << " "; 

        cout<<endl;

        cout<<"With reference : \n";

        for (int &x : vect) 
            x = x + 5;      // x is a reference to the vector elements 

        for (int x : vect) 
            cout << x << " "; 

        cout<<endl;

        return 0;
    }

Output:

    Without reference : 
    1 2 3 4 5 
    With reference : 
    6 7 8 9 10 

Explanation:

As we can see in the output above when we try to modify objects for each loop without using a reference, only the temporary objects are modified (and not the actual objects). However, while using a reference for each loop, the values in the vector change because now we are modifying the actual object itself.

Rules for Using Reference in Functions

  • While passing objects by reference, we need to remind ourselves that changes made to the parameters in the function will be reflected in the calling function.
  • To avoid accidental changes to the parameters, we can use constant references as parameters.
  • While returning references from a function, we must ensure that the scope of the variable whose reference is being returned is not limited to that function only.

Reference Collapsing

Since references in C++ are not separate objects, it is not allowed to create a reference to a reference in C++ with the usual syntax declaration. However, we can make use of type manipulations in typedef to create a reference to reference. In such cases, reference collapsing rules are followed. So, we can have four possible combinations :

  • Lvalue reference to lvalue reference
  • Lvalue reference to rvalue reference
  • Rvalue reference to lvalue reference
  • Rvalue reference to rvalue reference

Except for the last type above (rvalue reference to rvalue reference), all the other combinations are treated as lvalue references. Rvalue reference to rvalue reference is treated as an rvalue reference. The following code shows the four combinations :

#include <iostream>

using namespace std;

typedef int&  lref;
typedef int&& rref;

int main()
{
    int x = 10;

    lref&  ref1 = x;    // Lvalue reference to lvalue reference 
    // lref&  ref1 = 1; -> This is not allowed because ref1 is treated as lvalue reference
    
    lref&& ref2 = x;    // Lvalue reference to Rvalue reference
    // lref&& ref2 = 1; -> This is not allowed because ref2 is treated as lvalue reference
    
    rref&  ref3 = x;    // Rvalue reference to Lvalue reference
    // rref&  ref3 = 1; -> This is not allowed because ref3 is treated as lvalue reference
    
    rref&& ref4 = 1;    // Rvalue reference to Rvalue reference
    // rref&& ref4 = x; -> This is not allowed because ref4 is treated as rvalue reference

    return 0;
}

Returning Values by Reference

There are two important subjects related to references in C++: References as Parameters and References as Return Value. Passing references as function parameters have been covered in the "Properties of Reference" section. Functions can also return values by reference in C++. By doing so, we can use a function call statement as the left side of an expression. While returning a reference from a function, we must also ensure that the scope of the variable which is being referenced extends to the calling function. The following code makes this clear :

#include <iostream>

using namespace std;

int& func()
{
    int x = 5;
    // return x; -> This is not allowed because x is local to func()
    static int y = 10;
    cout<<"Value of x and y is "<<x<<" and "<<y<<endl;
    return y;   // This is allowed because y is static and is visible to main()
}

int main()
{
    func() = 200;    // Changing the value of y here
    func();

    return 0;
}

Output:

Value of x and y is 5 and 10
Value of x and y is 5 and 200

Explanation:

The func() function shown in the code above returns a reference to a static variable y. The visibility scope of y extends to main() since y is static. When the first function call is made, i.e. func() = 200; the current values of x and y are printed and a reference to y is returned by the function. Then the value of y is updated to 200. In other words, this is the same as writing y = 200; When the second function call is made, the current values of x and y are printed again and we can see that the value of y has been modified to 200 and the value of x remains the same as before.

References vs Pointers

ReferencesPointers
Reference must have a data typePointers can be of type void
Reference cannot be reassignedValue of a pointer can be changed to another address
Reference is declared using the & operatorPointer is declared using the * operator
There cannot be multiple indirection levels in references (except for double references using typedef manipulation)Pointers can have multiple levels of indirection (eg. double-pointer, triple pointer, etc)
References have to be initialized at the point of declaration. They cannot be NULLPointers can be NULL

Why are References Less Powerful Than Pointers?

  • References cannot be NULL, unlike pointers which can be NULL to denote that they are not pointing to anything.
  • References cannot be reassigned to another variable after they have been created.
  • Referenced must be initialized at the time of declaration.

Owing to such limitations, references are not as powerful as pointers. This is why pointers are preferred in the implementation of many popular data structures like Linked List, Tree, etc. JAVA, on the other hand, uses references to implement these data structures because it doesn't have such restrictions on references. This is why JAVA doesn't use pointers.

References are Safer and Easier to Use

  • Since references are initialized at the time of declaration, we don't need to worry about situations like wild pointers (uninitialized pointers which point to arbitrary memory locations).
  • References don't have to be dereferenced like pointers and can be used like normal variables (as an alias for some other variable).
  • It is necessary to pass objects as a reference in a copy constructor. Otherwise, if it's passed by value, the copy constructor would call itself to create a copy of the object which in turn would call another copy constructor and this loop would go on until the compiler runs out of memory.
  • While overloading operators like ++, references are used to ensure that the original variable is being returned and not a new copy.

Conclusion

  • References in C++ are an alias for other existing variables.
  • References must be initialized at the time of their declaration and cannot be reassigned.
  • There are different types of references such as lvalue references, rvalue references, and constant references.
  • Function parameters can be passed as references. Functions can return references as well.
  • References are less powerful than pointers but are safer and easier to use.
Challenge Time!
quiz Time to test your skills and win rewards! Note: Rewards will be credited after the next product update.