This Pointer in C++

Understanding Object's Memory Structure

Class objects only store data members (attributes or variables), but not member functions in memory, It refers to how memory is allocated in C++ for class instances (objects).

Object's Memory Layout:

An object of a class in C++ only needs to store its data members (variables) in memory. The member functions are shared across all instances (objects) of the class, meaning the functions are not duplicated for each object.

Example:
class Box {
public:
    int length;  // Data member

    // Member function
    void setLength(int len) {
        length = len;
    }
};

When we create objects of this class:

Box box1;
Box box2;

Both box1 and box2 will have separate memory allocated for their data members, but they share the same member functionsetLength() in memory. Each object has its own copy of length, but the function code is stored once and used by all objects.

Why Only Data Members Are Stored in Objects?

Objects are instances of classes, and they are meant to represent state (data), while functions define the behavior (operations) that can be performed on that state.

  • Data Members: These represent the attributes or properties of an object. Since each object can have its own unique values for these properties, they need their own memory space for data members. For example, box1 might have length = 10, while box2 might have length = 20.
    • Objects store their data members in memory on the stack or heap, depending on how they are created.
  • Member Functions: Functions represent the behavior that applies to any instance of the class. Since the logic of a function is the same for all objects, there is no need to store a separate copy of the function for each object. Instead, the function is stored once in memory, and all objects access the same function. They are stored in the text/code segment of memory, shared by all objects of the class.

Memory Layout Example

Let's visualize it with an example.

Code:

class Box {
public:
    int length;
    int width;

    void setDimensions(int len, int wid) {
        length = len;
        width = wid;
    }

    int calculateArea() {
        return length * width;
    }
};

Object Creation:

Box box1;
Box box2;

Here's a simplified diagram of how memory is allocated:

Memory for Objects:
+-------------------+        +-------------------+
|    Object: box1   |        |    Object: box2   |
+-------------------+        +-------------------+
| length = 10       |        | length = 20       |
| width  = 5        |        | width  = 10       |
+-------------------+        +-------------------+

Member Functions in Memory (shared by all objects):
+-------------------+
| setDimensions()   |
| calculateArea()   |
+-------------------+

Key Points:

  • box1 and box2 have their own space in memory to store their respective length and width values. Each object can have different values for these data members.
  • Member functions like setDimensions() and calculateArea() are stored once in memory and are shared by all instances of the class (box1, box2, etc.).
    • When box1.setDimensions(10, 5) is called, box1 uses the same function that box2 would use, but this points to the specific object invoking the function (so box1.length is set to 10 and box1.width is set to 5).

Why is This Efficient?

This design makes memory usage much more efficient:

  • Data members are specific to each object, so it makes sense for each object to have its own memory for its data.
  • Member functions contain the same code for all objects, so storing multiple copies of the same function for each object would be redundant and wasteful. By sharing the function code across all objects, we avoid unnecessary duplication.

What Actually Happens at the Machine Level?

At the machine level:

  • Data members are part of the object’s memory. When an object is created, memory is allocated for its data members.
  • Member functions are part of the class, not individual objects. They are compiled into machine code, and this code is loaded into memory once, regardless of how many objects are created. When a member function is called, the this pointer is implicitly passed to the function to indicate which object’s data members should be accessed or modified.

The member function itself does not belong to any specific object; it exists in memory as a piece of compiled code, and every object of that class uses the same function code.

How Member Functions Work Without Objects

Member functions do not require any objects to exist in memory because their instructions are independent of any specific object. The actual code for a member function is just a sequence of instructions, stored once, that will be executed whenever an object calls that function. The function can operate on different objects by using the this pointer, which points to the object that invoked the function.

Here’s what happens:

  • The function's code is loaded into memory once, at the time the program is compiled or when the program is loaded into memory (before any objects are created).
  • The data members (like length in a Box class) are stored in each object when the object is instantiated.
  • When you call a member function on an object, the same function code is executed for every object, but the data accessed by the function depends on the specific object that invoked it (using the this pointer).

Example:

Consider the following class:

class Box {
public:
    int length;  // Data member

    void setLength(int len) {
        length = len;  // Modifies the length of the object that invoked this function
    }

    int getLength() const {
        return length;  // Returns the length of the object that invoked this function
    }
};

Compilation Time

  • The code for setLength() and getLength() is compiled into machine instructions and stored in the text segment of the program’s memory, even before any object like box1 or box2 is created.

Object Creation

  • When Box box1 and Box box2 are instantiated, memory is allocated for their data members (like length), but no additional memory is needed for the member functions.

Function Call

  • When box1.setLength(10) is called, the same function code stored in memory is executed. The this pointer in the function will point to box1, so box1.length is set to 10.
  • Similarly, if box2.setLength(20) is called, the same function code is executed, but this time the this pointer points to box2, and box2.length is set to 20.

Example Code:

class Box {
public:
    int length;
    int width;

    void setDimensions(int len, int wid) {
        length = len;
        width = wid;
    }

    int calculateArea() const {
        return length * width;
    }
};
int main() {
    Box box1;
    Box box2;
    
    box1.setDimensions(5, 10);
    box2.setDimensions(7, 8);
    
    int area1 = box1.calculateArea();
    int area2 = box2.calculateArea();
}

Now let’s visualize what happens in memory.

Memory Sections

C++ programs divide memory into different segments:

  1. Text Segment: Stores the compiled code of functions, including member functions.
  2. Heap/Stack Segment: Stores dynamically allocated objects (heap) or local variables/objects (stack).
  3. Data Segment: Stores global and static variables (unrelated to this example).
Before Object Creation (Only Member Functions Loaded):
Before Object Creation (Only Member Functions Loaded)

+-------------------------------+
|  Text Segment (Code Section)  | 
|                               |
|  setDimensions()              | <-- Compiled once, shared by all objects
|  calculateArea()              | <-- Compiled once, shared by all objects
|                               |
+-------------------------------+

+-------------------------------+
|  Heap/Stack Segment           |
|                               |
|  (No objects yet)             |
|                               |
+-------------------------------+
After Object Creation (Memory for Objects):
After Object Creation (Memory for Objects)

+-------------------------------+
|  Text Segment (Code Section)  |
|                               |
|  setDimensions()              | <-- Shared code, used by both box1 and box2
|  calculateArea()              | <-- Shared code, used by both box1 and box2
|                               |
+-------------------------------+

+-------------------------------+
|  Stack/Heap Segment           |
|                               |
|  Object: box1                 |
|  +------------------------+   |
|  | length = 5             |   |
|  | width  = 10            |   |
|  +------------------------+   |
|                               |
|  Object: box2                 |
|  +------------------------+   |
|  | length = 7             |   |
|  | width  = 8             |   |
|  +------------------------+   |
+-------------------------------+
  • Text Segment: The member functions setDimensions() and calculateArea() are still stored once and shared by all objects.
  • Heap/Stack Segment:
    • Memory for the data members of box1 and box2 is allocated separately on the stack (or heap if dynamically allocated). Each object has its own copy of the data members (length and width), but they share the same function code.
    • box1.length is 5 and box1.width is 10 after calling box1.setDimensions(5, 10).
    • box2.length is 7 and box2.width is 8 after calling box2.setDimensions(7, 8).
When a Function is Called:

Now, when we call box1.calculateArea() or box2.calculateArea(), the same function code stored in the Text Segment is executed, but the this pointer differentiates which object's data is being used.

+------------------------------+
|  Text Segment (Code Section) |
|                              |
|  setDimensions()             | <- Shared code executed, uses box1 

|                              |    or box2's data
|  calculateArea()             | <- Shared code executed, uses box1
|                              |    or box2's data
+------------------------------+

+------------------------------+
|  Stack/Heap Segment          |
|                              |
|  Object: box1                |   box1.calculateArea()
|  +-----------------------+   |   --> this->length = 5
|  | length = 5            |   |   --> this->width  = 10
|  | width  = 10           |   |   Result: 50
|  +-----------------------+   |
|                              |
|  Object: box2                |   box2.calculateArea()
|  +-----------------------+   |   --> this->length = 7
|  | length = 7            |   |   --> this->width  = 8
|  | width  = 8            |   |   Result: 56
|  +-----------------------+   |
+------------------------------+
  • Text Segment: The function calculateArea() is called for both box1 and box2, but the function logic remains the same. The this pointer makes sure the function operates on the correct object's data members.
  • Stack/Heap Segment:
    • When box1.calculateArea() is called, this->length points to box1.length (5), and this->width points to box1.width (10). The result is 50.
    • When box2.calculateArea() is called, this->length points to box2.length (7), and this->width points to box2.width (8). The result is 56.

This Pointer

As you must have studied in above sections, that class's member function are stored only once common for all objects, while the data members of the object is stored at different location for every object.

So how does they link up, like we call a member function by using any object, how does it called up?

The answer is the this pointer.

The this pointer is a special pointer available in non-static member functions of a class. It points to the object that is invoking the member function. The this pointer is implicitly passed to all non-static member functions of a class when they are called, allowing the function to know which specific object the function is being invoked on.

When a member function is called on an object, the this pointer automatically points to the object that invoked the function, ensuring the correct object's data members are used inside the function.

Every non-static member function of a class has access to a hidden pointer called this. The this pointer is a pointer to the current instance (object) of the class that is invoking the member function. This allows the member function to refer to the data members (variables) of the object that invoked it.

  • If box1 calls setDimensions(), the this pointer points to box1.
  • If box2 calls setDimensions(), the this pointer points to box2.

This ensures that the member function modifies the correct object’s data members, even though the function itself is shared by all instances.

When you call a member function, C++ automatically passes the this pointer as an additional, hidden argument to the function. This pointer points to the object on which the function was called.

Example:
class Box {
public:
    int length;
    int width;

    void setDimensions(int len, int wid) {
        length = len;  // Equivalent to this->length = len
        width = wid;   // Equivalent to this->width = wid
    }

    int calculateArea() const {
        return length * width;  // Equivalent to this->length * this->width
    }
};
int main() {
    Box box1;
    box1.setDimensions(5, 10);
    int area = box1.calculateArea();
}
When we call:

box1.setDimensions(3, 10);

Under the hood, C++ transforms this call into something like:
Box::setDimensions(&box1, 5, 10);

Here, &box1 (the address of box1) is implicitly passed as the first argument to the setDimensions() function. This address is what the this pointer refers to inside the function.

So, inside setDimensions(), the this pointer points to box1, allowing the function to modify box1's length and width.

Similarly, box1.calculateArea() becomes calculateArea(&box1),
where &box1 is passed as the `this` pointer.

Static Member Functions and Data Members

Static member functions: These belong to the class itself, not to any object. They can be called without creating an object, and they do not have access to the this pointer because they are not associated with any specific instance of the class.

Static data members: These are also shared by all objects of a class. Unlike regular data members, static data members are stored in a single location in memory, and all objects of that class share this one value.

class Box {
public:
    static int objectCount;

    Box() {
        objectCount++;
    }
};

// Definition of static member
int Box::objectCount = 0;

int main() {
    Box box1, box2;
    std::cout << Box::objectCount;  // Outputs: 2
}

In this example, the static member objectCount is shared across all objects of Box. No matter how many objects we create, they all share the same value for objectCount.

Key Points about this Pointer

1 this is implicit:

You don’t have to explicitly pass the this pointer when calling a member function; it is automatically passed by the compiler.

2 Non-static member functions:

The this pointer is only available in non-static member functions. Static member functions don’t have a this pointer because they don’t operate on specific objects.

3 Points to the invoking object:

The this pointer points to the current object that invoked the member function.

Means this pointer has the address of the memory location where that particular object is stored.

4 Constant and cannot be changed:

The value of the this pointer (i.e., the address of the current object) cannot be modified.

Syntax and Usage of this

When a non-static member function is called, this holds the address of the object that called the function, For example:

#include <iostream>

using namespace std;

class MyClass {
public:
    int value;

    void setValue(int value) {
        this->value = value;  // Use of 'this' to distinguish between the member variable and the parameter
    }

    void showAddress() {
        std::cout << "Address of the current object: " << this << std::endl;
    }
};


int main() {
    MyClass obj1;
    obj1.setValue(1);
    obj1.showAddress();
    
    MyClass obj2;
    obj2.setValue(2);
    obj2.showAddress();
    
}

In the setValue function:

  • The this pointer is used to reference the object's member variable value to distinguish it from the local parameter value.
  • The expression this->value refers to the member variable, whereas value without this refers to the function parameter.
Output:
Address of the current object: 0x505298
Address of the current object: 0x505290
  • Note: The address would be different in your machine.
+----------------------------------+
|  Text Segment (Code Section)     |
|                                  |
|  setValue()                      | <-- Shared code for both objects
|  showAddress()                   | <-- Shared code for both objects
+----------------------------------+

+----------------------------------+
|  Stack/Heap Segment              |
|                                  |
|  Object: obj1                    |   'this' points to obj1 when obj1 
|  +---------------------------+   |    calls a function
|  | value = 1                 |   |    0x505298
|  +---------------------------+   |
|                                  |
|                                  |
|  Object: obj2                    |   'this' points to obj2 when obj2
|  +---------------------------+   |    calls a function
|  | value = 2                 |   |    0x505290

|  +---------------------------+   |
+----------------------------------+

Purpose of the This Pointer:

At its core, the “this” pointer is a hidden pointer that points to the instance of the class for which a member function is invoked. It allows differentiation between class members and parameters with the same, ensuring that correct instance is referenced within the member function. Thus we can say that, “this” is a const pointer that holds the address of the current implicit object.

class MyClass {
private:
    int data;

public:
    void setData(int data) {
        // Using 'this' to differentiate between member and parameter
        this->data = data;
    }
};

Syntax and Usage:

The “this” pointer is implicitly available within non-static member function of a class. Its usage is straightforward: you can use it to access class members and invoke other member functions. The “this” pointer is particularly useful in scenarios where class members and  parameters have the same name.

class MyClass {
private:
    int data;

public:
    void setData(int data) {
        // Using 'this' to access the class member
        this->data = data;
    }

    int getData() {
        // Using 'this' is optional but can enhance clarity
        return this->data;
    }
};

How the this Pointer Helps

1️⃣ Distinguishing Between Object Members and Function Parameters

When a member function parameter has the same name as a class member variable, the this pointer is used to distinguish between them. This is critical to avoid ambiguity in class functions.

Example:
class MyClass {
    int value;
public:
    void setValue(int value) {
        this->value = value;  // 'this->value' refers to the member variable, while 'value' is the parameter
    }
};
  • Purpose: this->value refers to the member variable value, while value without this refers to the function's parameter.

2️⃣Accessing the Calling Object

The this pointer gives access to the object that called the member function. Each object has its own copy of the member variables, and the this pointer provides a reference to the correct set of member variables for the calling object.

The this pointer is essential in differentiating between objects. Although member functions are shared by all objects, the this pointer allows each object to invoke the function in the context of its own data members.

Example:
class Box {
    int length;
public:
    void setLength(int l) {
        this->length = l;  // 'this' points to the specific object that called this function
    }
};
  • Purpose: Ensures that the function modifies the correct instance of Box when called.

3️⃣ Enabling Method Chaining

The this pointer allows a member function to return the calling object itself (i.e., *this). This enables method chaining, where multiple functions can be called on the same object in a single statement.

Example:
class Box {
    int length;
public:
    Box& setLength(int l) {
        this->length = l;
        return *this;  // Return the object itself (dereferenced from 'this')
    }

    void display() const {
        std::cout << "Length: " << length << std::endl;
    }
};

int main() {
    Box b;
    b.setLength(10).display();  // Method chaining: setLength returns the object, allowing display to be called immediately
}
  • Purpose: Returning *this allows functions to be chained together for cleaner, more concise code.

4️⃣ Avoiding Self-Assignment in Operator Overloading

When overloading the assignment operator (=), the this pointer is used to check for self-assignment. This ensures that an object doesn't accidentally overwrite itself, which could lead to memory issues.

Example:
class MyClass {
    int* data;
public:
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {  // Avoid self-assignment
            delete data;
            data = new int(*other.data);
        }
        return *this;
    }
};
  • Purpose: Ensures that an object does not try to assign itself, preventing memory leaks or other unintended behavior.

5️⃣ Handling Constant Member Functions

In constant member functions (functions declared with const), the this pointer points to a const version of the object (const MyClass*), preventing modification of the object’s member variables.

Example:
class MyClass {
    int value;
public:
    void display() const {
        std::cout << "Value: " << value << std::endl;
        // 'this' is treated as 'const MyClass*' so no modifications to 'value' are allowed
    }
};
  • Purpose: Enforces that the function cannot modify the object, ensuring the immutability of the object in the context of the function.

6️⃣ Enabling Object Comparison

The this pointer can be used in member functions to compare one object with another. It allows the function to refer to the current object and compare it against another object passed as an argument.

Example:
class Box {
    int length;
public:
    bool isEqual(Box& other) {
        return this->length == other.length;  // Compare current object with another
    }
};
  • Purpose: Allows member functions to compare the current object (this) with another object.

this pointer and Member Function Chaining:

The “this” pointer plays a crucial role in enabling member function chaining, where multiple member functions are invoked in sequential manner. It can be useful to have a member function return the implicit object as a return value. The primary reason to do this is to allow member functions to be "chained" together, so several functions can be called on the same object in a single expression. This is called function chaining (or method chaining).

class ChainedExample {
private:
    int value;

public:
    ChainedExample& setValue(int value) {
        // Using 'this' for member function chaining
        this->value = value;
        return *this;
    }

    int getValue() const {
        return this->value;
    }
};

Consider the following example:

class Calc
{
private:
    int m_value{};

public:

    void add(int value) { m_value += value; }
    void sub(int value) { m_value -= value; }
    void mult(int value) { m_value *= value; }

    int getValue() { return m_value; }
};

If we wanted to add 5, subtract 3, and multiply by 4, you would have to do this:

#include <iostream>

int main()
{
    Calc calc{};
    calc.add(5); // returns void
    calc.sub(3); // returns void
    calc.mult(4); // returns void

    std::cout << calc.getValue() << '\n';

    return 0;
}

However, if we make each function return *this by reference, we can chain the calls together. Here is the new version of Calc with “chainable” functions:

class Calc
{
private:
    int m_value{};

public:
    Calc& add(int value) { m_value += value; return *this; }
    Calc& sub(int value) { m_value -= value; return *this; }
    Calc& mult(int value) { m_value *= value; return *this; }

    int getValue() { return m_value; }
};

Note that add(), sub() and mult() are now returning *this by reference. Consequently, this allows us to do the following:

#include <iostream>

int main()
{
    Calc calc{};
    calc.add(5).sub(3).mult(4); // method chaining

    std::cout << calc.getValue() << '\n';

    return 0;
}

We have effectively condensed three lines into one expression.

First, calc.add(5) is called, which adds 5 to m_value.add() then returns *this, which is just a reference to implicit object calc, so calc will be the object used in subsequent evaluation. Next calc.sub(3) evaluates, which subtracts 3 from m_value and again returns calc. Finally, calc.mult(4) multiplies m_value by 4 and returns calc, which isn't used further, and thus ignored.

Resetting a class back to default state

If your class has a default constructor, you may interested in providing a way to return an existing object back to its default state.

The best way to reset a class back to a default state is to create a reset() member function, have that function creates a new object (using the default), and then assign that new object to the current implicit object like this:

void reset()
{
    *this = {}; // value initialize a new object and overwrite the implicit object
}

Here's a full program demonstrating this reset() function in action:

#include <iostream>

class Calc
{
private:
    int m_value{};

public:
    Calc& add(int value) { m_value += value; return *this; }
    Calc& sub(int value) { m_value -= value; return *this; }
    Calc& mult(int value) { m_value *= value; return *this; }

    int getValue() { return m_value; }

    void reset() { *this = {}; }
};


int main()
{
    Calc calc{};
    calc.add(5).sub(3).mult(4);

    std::cout << calc.getValue() << '\n'; // prints 8

    calc.reset();

    std::cout << calc.getValue() << '\n'; // prints 0

    return 0;
}
Output:
8
0