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 havelength = 10
, whilebox2
might havelength = 20
.- Objects store their data members in memory on the
stack
orheap
, depending on how they are created.
- Objects store their data members in memory on the
- 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
andbox2
have their own space in memory to store their respectivelength
andwidth
values. Each object can have different values for these data members.- Member functions like
setDimensions()
andcalculateArea()
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 thatbox2
would use, butthis
points to the specific object invoking the function (sobox1.length
is set to 10 andbox1.width
is set to 5).
- When
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 aBox
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()
andgetLength()
is compiled into machine instructions and stored in the text segment of the program’s memory, even before any object likebox1
orbox2
is created.
Object Creation
- When
Box box1
andBox box2
are instantiated, memory is allocated for their data members (likelength
), 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. Thethis
pointer in the function will point tobox1
, sobox1.length
is set to10
. - Similarly, if
box2.setLength(20)
is called, the same function code is executed, but this time thethis
pointer points tobox2
, andbox2.length
is set to20
.
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:
- Text Segment: Stores the compiled code of functions, including member functions.
- Heap/Stack Segment: Stores dynamically allocated objects (heap) or local variables/objects (stack).
- 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()
andcalculateArea()
are still stored once and shared by all objects. - Heap/Stack Segment:
- Memory for the data members of
box1
andbox2
is allocated separately on the stack (or heap if dynamically allocated). Each object has its own copy of the data members (length
andwidth
), but they share the same function code. box1.length
is 5 andbox1.width
is 10 after callingbox1.setDimensions(5, 10)
.box2.length
is 7 andbox2.width
is 8 after callingbox2.setDimensions(7, 8)
.
- Memory for the data members of
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 bothbox1
andbox2
, but the function logic remains the same. Thethis
pointer makes sure the function operates on the correct object's data members. - Stack/Heap Segment:
- When
box1.calculateArea()
is called,this->length
points tobox1.length
(5), andthis->width
points tobox1.width
(10). The result is 50. - When
box2.calculateArea()
is called,this->length
points tobox2.length
(7), andthis->width
points tobox2.width
(8). The result is 56.
- When
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
callssetDimensions()
, thethis
pointer points tobox1
. - If
box2
callssetDimensions()
, thethis
pointer points tobox2
.
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 variablevalue
to distinguish it from the local parametervalue
. - The expression
this->value
refers to the member variable, whereasvalue
withoutthis
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 variablevalue
, whilevalue
withoutthis
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