Skip to content

Commit

Permalink
Memory management
Browse files Browse the repository at this point in the history
  • Loading branch information
JSH32 committed Jan 12, 2024
1 parent 4762f6f commit 45269fb
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 8 deletions.
9 changes: 5 additions & 4 deletions codex-cpp/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@
- [Exception Handling](./oop/exceptions/index.md)
- [Use of try, catch and throw](./oop/exceptions/try_catch_throw.md)
- [Custom Exceptions](./oop/exceptions/custom_exceptions.md)
- [Smart Pointers](./oop/smart_pointers/index.md)
- [Unique Pointers](./oop/smart_pointers/unique.md)
- [Shared Pointers](./oop/smart_pointers/shared.md)
- [Weak Pointers](./oop/smart_pointers/weak.md)
- [Memory Management](./memory_management/index.md)
- [Stack vs Heap](./memory_management/stack_heap.md)
- [Unique Pointers](./memory_management/unique.md)
- [Shared Pointers](./memory_management/shared.md)
- [Weak Pointers](./memory_management/weak.md)
- [Lambda Expressions](./lambda.md)
- [Advanced Data Structures](./advanced_ds/index.md)
- [Time Complexity and Big-O Notation](./advanced_ds/complexity.md)
Expand Down
10 changes: 10 additions & 0 deletions codex-cpp/src/memory_management/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Memory Management in C++

Memory management is an integral part of developing applications in C++. It's the practice of controlling how the memory in a computer program is allocated, used, and freed. Proper memory management is critical because it directly impacts program performance and stability.

Just as a strong foundation is necessary when constructing a house, a solid understanding of memory management is crucial for building reliable and efficient C++ programs.

- [Stack vs Heap](./stack_heap.md)
- [Unique Pointers](./unique.md)
- [Shared Pointers](./shared.md)
- [Weak Pointers](./weak.md)
81 changes: 81 additions & 0 deletions codex-cpp/src/memory_management/shared.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Shared Pointers

`std::shared_ptr` is another type of smart pointer provided by the C++ standard library to manage dynamically allocated objects. Unlike `std::unique_ptr`, `std::shared_ptr` allows multiple shared pointers to point to the same object. The object is automatically destroyed and its memory deallocated when the last `std::shared_ptr` owning it is destroyed or reset.

## The Concept of Reference Counting

`std::shared_ptr` uses an internal reference count mechanism to keep track of how many `std::shared_ptr` instances own the same resource. When a new `std::shared_ptr` is created from another, the reference count is incremented. When a `std::shared_ptr` is destructed, the count is decremented. Once the count reaches zero, meaning no `std::shared_ptr` owns the resource, the resource is destroyed.

## Advantages of `std::shared_ptr`

- **Shared Ownership**: The resource can be shared by multiple `std::shared_ptr` instances, enabling more flexible memory management.
- **Automatic Memory Management**: Like `std::unique_ptr`, `std::shared_ptr` automatically releases the memory when no longer needed.
- **Thread Safe**: The reference count mechanism is thread-safe (except for simultaneous reads and writes to the same `std::shared_ptr`).

## Usage of `std::shared_ptr`

Use `std::make_shared` to create a `std::shared_ptr`. This function will allocate the object and initialize the reference count.

~~~admonish example title="Creating a `std::shared_ptr`"
```cpp,editable
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sharedPtr1 = std::make_shared<int>(10);
std::cout << "sharedPtr1 Value: " << *sharedPtr1 << std::endl;
{
// Create another shared pointer sharing ownership of the same int
std::shared_ptr<int> sharedPtr2 = sharedPtr1;
std::cout << "sharedPtr2 Value: " << *sharedPtr2 << std::endl;
// Both sharedPtr1 and sharedPtr2 are owners now
}
// sharedPtr2 is out of scope and is destroyed, but the int remains as sharedPtr1 still owns it
std::cout << "sharedPtr1 Value after sharedPtr2 is destroyed: " << *sharedPtr1 << std::endl;
// When main exits, sharedPtr1 is also destroyed, and the managed int is deallocated
return 0;
}
```
~~~

It's important to note that `std::make_shared` is more efficient than separately allocating the object and the internal control block because it can perform a single heap allocation for both, reducing the overhead and improving performance.

## Caveats with `std::shared_ptr`

While `std::shared_ptr` is quite powerful, it's important to be aware of potential pitfalls:

- **Cyclic References**: If circular references are created (e.g., two or more `std::shared_ptr` instances own each other directly or indirectly), the reference count can't reach zero, leading to memory leaks.
- **Performance Overhead**: The extra control block, reference counting, and thread-safe increment/decrement operations introduce overhead compared to `std::unique_ptr`.

## Using Custom Deleters

`std::shared_ptr` and `std::unique_ptr` can both be used with a custom deleter.

~~~admonish example title="Custom Deleter with `std::shared_ptr`"
```cpp,editable
#include <iostream>
#include <memory>
struct Resource {
~Resource() { std::cout << "Resource freed." << std::endl; }
};
int main() {
auto deleter = [](Resource* r) {
std::cout << "Custom deleter called." << std::endl;
delete r;
};
std::shared_ptr<Resource> sharedPtr1(new Resource, deleter);
std::shared_ptr<Resource> sharedPtr2 = sharedPtr1; // sharedPtr2 shares ownership
// Resources are freed when the last shared_ptr (sharedPtr2) goes out of scope.
return 0;
}
```
This example will call the deleter when the `std::shared_ptr` goes out of scope. By default it will just call the desctructor.
~~~

`std::shared_ptr` is an incredibly versatile tool. By understanding how to properly create, use, and avoid pitfalls with `std::shared_ptr`, you can safely manage shared resources in complex C++ applications.
81 changes: 81 additions & 0 deletions codex-cpp/src/memory_management/stack_heap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Stack vs Heap Memory Allocation

Understanding memory allocation is essential when diving into C++ programming. Let's explore two key areas where memory is allocated: the **stack** and the **heap**. Both serve different purposes in a program's life cycle and have various access patterns and management styles.

## Stack Memory Allocation

The stack is an area of memory that stores temporary data such as function parameters, return addresses, and local variables. It operates on a last-in, first-out (LIFO) model, which means that data added last will be the first to be removed when no longer needed.

~~~admonish example title="Local Variable on the Stack"
```cpp
void someFunction() {
int localVar = 5; // Allocated on the stack
// 'localVar' is only visible within 'someFunction'
} // 'localVar' is automatically deallocated when 'someFunction' returns
```
~~~


### Advantages of Stack Memory:

- **Fast Allocation**: Memory management on the stack is simple and thus very fast.
- **Automatic Memory Management**: Once the scope ends, variables on the stack are automatically deallocated.

### Disadvantages of Stack Memory:

- **Size Limitation**: Stack size is limited and can lead to stack overflow if exceeded.
- **Local Scope**: Memory on the stack can only be used within the function that allocated it.

## Heap Memory Allocation

The heap is a pool of memory used for dynamic allocation. Unlike stack variables, which are managed automatically, heap variables need to be explicitly allocated and deallocated by the programmer.

~~~admonish example title="Dynamic Allocation on the Heap"
```cpp
int* heapVar = new int; // Allocate memory for an integer on the heap
*heapVar = 5; // Store 5 in the allocated memory
// ... Use 'heapVar' as needed ...
delete heapVar; // Deallocate memory when done
```
~~~

### Advantages of Heap Memory:

- **Large Amounts of Memory**: The heap can provide large blocks of memory that would not fit on the stack.
- **Persistent until Deallocated**: Memory on the heap remains allocated until it is explicitly deallocated or the program ends.

### Disadvantages of Heap Memory:

- **Slower than Stack**: Accessing memory on the heap is slower than stack memory access.
- **Manual Management**: The programmer is responsible for allocating and deallocating heap memory, which can lead to errors.

## C-Style Memory Allocation with `malloc`

Before C++, the C language used `malloc()` to allocate memory dynamically. `malloc()` does not call constructors and therefore is not suitable for non-POD (plain old data) types in C++.

### Example: C-Style Allocation

~~~admonish example title="C-Style Allocation with malloc"
```cpp
#include <cstdlib> // Required for malloc and free
int* cStyleVar = (int*)malloc(sizeof(int)); // Allocates enough memory for an int
if (cStyleVar != nullptr) {
*cStyleVar = 5; // Use the allocated memory to store the value 5
}
// ... Work with 'cStyleVar' ...
free(cStyleVar); // Deallocate memory with free
```
~~~

The `sizeof` operator in these examples is used to calculate the size in bytes of a type or object. In the above case, `sizeof(int)` provides the amount of memory required to store one integer.

~~~admonish info title="What does `malloc` do?"
The `malloc` function in C-style memory allocation is used for dynamically allocating a block of memory on the heap. It takes the size of the memory needed (in bytes) as a parameter, and it returns a pointer to the beginning of that block of memory.
~~~

In modern C++, `malloc` and `free` are generally discouraged. Instead, the language provides operators `new` and `delete` for dynamic memory allocation, which ensure that object constructors and destructors are properly called. For safer memory allocation, developers are further encouraged to use smart pointers which we will talk about in the next section.
69 changes: 69 additions & 0 deletions codex-cpp/src/memory_management/unique.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Unique Pointers

Smart pointers are a key feature of modern C++ that handle the allocation and deallocation of memory for you. `std::unique_ptr` is particularly useful because it manages a dynamically allocated object and automatically deletes the object when the `std::unique_ptr` goes out of scope.

## Benefits of `std::unique_ptr`

- **Automatic Resource Management**: Automatically frees the associated memory without needing explicit `delete` calls.
- **Exclusive Ownership**: Ensures there's exactly one owner for the allocated memory, avoiding potential issues with multiple deletions.
- **Move Semantics**: Enables safe transfer of ownership from one `std::unique_ptr` to another.

## Creating and Using `std::unique_ptr`

Use `std::make_unique` to create a `std::unique_ptr` in a safe and convenient way.

### Example: Using `std::make_unique`

~~~admonish example title="Creating a `std::unique_ptr`"
```cpp,editable
#include <iostream>
#include <memory>
int main() {
auto uniquePtr = std::make_unique<int>(10); // Create a unique_ptr managing an int
std::cout << "Value: " << *uniquePtr << std::endl; // Use the managed object
// uniquePtr is automatically freed when going out of scope
return 0;
}
```
~~~

`std::make_unique` is the preferred method to create a `std::unique_ptr` because it ensures memory safety, even in cases where exceptions might occur.

## Transferring Ownership with Move Semantics

Since `std::unique_ptr` cannot be copied, the ownership of the managed memory can be transferred using move semantics, by using the `std::move` function.

### Example: Moving a `std::unique_ptr`

~~~admonish example title="Transferring Ownership"
```cpp,editable
#include <iostream>
#include <memory>
void processPointer(std::unique_ptr<int> ptr) {
std::cout << "Processing value: " << *ptr << std::endl;
// ptr will be automatically freed when the function scope ends
}
int main() {
auto owner = std::make_unique<int>(20);
processPointer(std::move(owner)); // Moves the ownership to processPointer
if (owner) {
std::cout << "Owner still has the unique_ptr." << std::endl;
} else {
std::cout << "Owner no longer has the unique_ptr." << std::endl;
}
// Safe to exit main, no manual delete needed
return 0;
}
```
~~~

~~~admonish tip title="What Are Move Semantics?"
Move semantics in C++ allow you to efficiently transfer ownership of resources (like dynamically allocated memory) from one object to another. Instead of copying the data, moving an object transfers its internal data to a new object and leaves the original object in a valid but unspecified state (often empty or null). This process is especially important for managing resources in a safe and performance-optimized manner.
~~~

Remember, when using `std::unique_ptr`, no need to worry about `delete`, thanks to its automatic memory management. This smart pointer ensures that your dynamically allocated memory is released when it’s no longer needed, which helps you to avoid memory leaks and keep your resource management clean and straightforward.
65 changes: 65 additions & 0 deletions codex-cpp/src/memory_management/weak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Weak Pointers

`std::weak_ptr` is a smart pointer that holds a non-owning ("weak") reference to an object that is managed by `std::shared_ptr`. It is designed to be used in conjunction with `std::shared_ptr` to overcome certain problems with shared ownership, particularly the issue of cyclic references.

## What is `std::weak_ptr`?

`std::weak_ptr` provides a way to reference an object managed by a `std::shared_ptr` without increasing its reference count. This is useful when you want to observe an object, but its existence should not influence its own lifetime.

## Key Features of `std::weak_ptr`

- **Cyclic References**: It helps to break cycles of `std::shared_ptr` that may lead to memory leaks.
- **Temporary Access**: It can be used to check if an object exists before accessing it through a `std::shared_ptr`.

## Using `std::weak_ptr`

To create a `std::weak_ptr`, you can construct it from a `std::shared_ptr`. To actually work with the underlying object, you must convert the `std::weak_ptr` to a `std::shared_ptr` using the `lock` method, which creates a shared ownership only if the managed object has not been deleted.

~~~admonish example title="Working with `std::weak_ptr`"
```cpp,editable
#include <iostream>
#include <memory>
class Example {
public:
void showMessage() { std::cout << "Message from object." << std::endl; }
~Example() { std::cout << "Example object destroyed." << std::endl; }
};
int main() {
std::weak_ptr<Example> weakPtr;
{
auto sharedPtr = std::make_shared<Example>();
weakPtr = sharedPtr; // weakPtr points to "Example" object
if (auto tempSharedPtr = weakPtr.lock()) {
// Converts to shared_ptr to check and use the object
tempSharedPtr->showMessage();
} else {
std::cout << "The object no longer exists." << std::endl;
}
}
// sharedPtr goes out of scope, "Example" object is destroyed
// Checks if the object weakPtr points to is already destroyed
if (weakPtr.expired()) {
std::cout << "The object has been destroyed." << std::endl;
}
return 0;
}
```
~~~

## Advantages of `std::weak_ptr`

- **Avoids Cyclical References**: By using `std::weak_ptr` for back-pointers, you can avoid cyclical references that cause memory leaks.
- **Safe Object Observers**: It provides a safe way to observe an object that might get deleted by another part of the program.

## Disadvantages of `std::weak_ptr`

- **No Direct Access**: You cannot directly access the object a `std::weak_ptr` refers to; you must convert it to a `std::shared_ptr` first.
- **Extra Check Required**: Before using the managed object, you have to perform an additional check to see if the object still exists.

Using `std::weak_ptr` can significantly increase the robustness of your programs when dealing with complex data structures that involve shared ownership. It is a powerful tool that should be understood and utilized to write high-quality C++ code without memory management issues.
1 change: 0 additions & 1 deletion codex-cpp/src/oop/smart_pointers/index.md

This file was deleted.

1 change: 0 additions & 1 deletion codex-cpp/src/oop/smart_pointers/shared.md

This file was deleted.

1 change: 0 additions & 1 deletion codex-cpp/src/oop/smart_pointers/unique.md

This file was deleted.

1 change: 0 additions & 1 deletion codex-cpp/src/oop/smart_pointers/weak.md

This file was deleted.

0 comments on commit 45269fb

Please sign in to comment.