Memory
Before C++11, you had to manage memory explicitly, leading to potential memory leaks and dangling pointers.
new
Operator
The new
operator allocates memory for an object or array of objects from the heap and returns a pointer to the allocated memory.
It also calls the constructor if you’re allocating an object of a class.
// Allocates memory for a single integer
int* ptr = new int;
// Allocates memory for an array of 10 integers
int* arr = new int[10];
// Allocates memory and calls the constructor for MyClass
MyClass* obj = new MyClass();
delete
Operator
The delete
operator deallocates memory that was previously allocated with new
.
If you allocated an array with new[]
, you must use delete[]
to deallocate it.
// Deallocates memory for a single integer
delete ptr;
// Deallocates memory for an array
delete[] arr;
// Deallocates memory and calls the destructor for MyClass
delete obj;
Memory Leaks: Always ensure to deallocate memory with
delete
to avoid memory leaks.
Deleting a null pointer (nullptr
) is safe and has no effect.
Always matchnew
withdelete
andnew[]
withdelete[]
.
Smart Pointers
they manage the lifetime of dynamically allocated objects, ensuring that memory is automatically deallocated when no longer needed.
They help avoid manual memory management mistakes such as memory leaks, dangling pointers, and double deletion.
They are 3 primary types of smart pointers:
- std::unique_ptr
- std::shared_ptr
- std::weak_ptr
unique_ptr
it represents exclusive ownership of an object, only one unique_ptr
can points to a specific object at any time.
When the unique_ptr
goes out of scope or is explicitly reset, the objects is destroyed, and the memory is freed.
It cannot be copied but can be moved to transfer ownership.
// Create a unique_ptr
std::unique_ptr ptr1 = std::make_unique(10);
// Transfer ownership from ptr1 to ptr2
std::unique_ptr ptr2 = std::move(ptr1);
shared_ptr
it allows shared ownership of an object. Multiple shared_ptr
instances can point to the same object, and the object is only destroyed when the last shared_ptr
goes out of scope.
It uses a reference counter to track how many shared_ptr
point to the object, and when the counter reaches zero, the object is destroyed.
// Create a shared_ptr
std::shared_ptr sptr1 = std::make_shared(20);
// Shared ownership
std::shared_ptr sptr2 = sptr1;
weak_ptr
It works with shared_ptr
to prevent circular references.
A weak_ptr
doesn’t increase the reference count, and it doesn’t own the object.
You can use lock()
to access the object (as a shared_ptr
) if it still exists.
std::shared_ptr sptr = std::make_shared(30);
// Doesn't increase reference count
std::weak_ptr wptr = sptr;
RAII
RAII (Resource Acquisition Is Initialization): A technique in C++ where resource management (e.g., memory, file handles) is tied to object lifetimes, ensuring that resources are released when objects go out of scope.
- Resource Acquisition: When an object is constructed, it acquires some resources (e.g., memory, file, network socket).
- Resource Release: When the object goes out of scope (is destroyed), the destructor automatically releases the resource.
Modern C++ uses move semantics (move constructor
and move assignment operator
) to efficiently transfer resources between objects.
Move semantics
it is allowing the transfer of resources from one object to another without copying the data.
Move semantics are invoked when:
- Returning large objects from a function.
- Passing temporary objects (rvalues) to a function.
- Assigning or initializing from an rvalue.
Rvalue and Lvalue
- Lvalue: An object that has a name and a persistent address (e.g., variables).
- Rvalue: A temporary object without a persistent address (e.g., result of an expression or literal).
void foo(int& lvalue) {
// lvalue reference
}
void foo(int&& rvalue) {
// rvalue reference - can be moved
}
int x = 10;
foo(x); // Calls lvalue version
foo(20); // Calls rvalue version
foo(std::move(x)); // Turns x into an rvalue, calls rvalue version
Prior to C++11, passing objects by value or returning them by value involved copying data, which could be slow for large objects.
Move semantics solve this problem by introducing move constructors and move assignment operators, which "move" the resource rather than copying it.
#include < iostream>
#include < utility> // for std::move
class MyClass {
private:
int* data; // Dynamic resource (heap memory)
public:
// Constructor
MyClass(int value) : data(new int(value)) {
std::cout << "Constructor: Resource acquired for " << *data << std::endl;
}
// Move Constructor
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr; // Leave the source object in a valid state
std::cout << "Move Constructor: Resource moved" << std::endl;
}
// Destructor
~MyClass() {
if (data) {
std::cout << "Destructor: Resource released for " << *data << std::endl;
delete data;
} else {
std::cout << "Destructor: No resource to release" << std::endl;
}
}
// Move Assignment Operator
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete data; // Release existing resource
data = other.data; // Steal other's resource
other.data = nullptr; // Reset the other object
std::cout << "Move Assignment: Resource moved" << std::endl;
}
return *this;
}
};
int main() {
MyClass obj1(10); // Regular construction
MyClass obj2 = std::move(obj1); // Move construction
MyClass obj3(20); // Another object
obj3 = std::move(obj2); // Move assignment
}