Memory Management Complete Guide
Pointer Types Advanced Topic

C++ Pointers: Complete Guide to All Pointer Types

Master C++ pointers with comprehensive examples of all pointer types, memory management, smart pointers, pointer arithmetic, and practical applications. Learn efficient memory manipulation techniques.

Basic Pointers

Memory addresses

Advanced Pointers

Complex types

Smart Pointers

Memory safety

Pointer Arithmetic

Memory navigation

Understanding Pointers in C++

Pointers are powerful features of C++ that store memory addresses. They enable direct memory access, dynamic memory allocation, and efficient data manipulation. Mastering pointers is essential for advanced C++ programming.

Basic Pointer Memory Address

Stores memory address of a variable. Foundation of pointer concepts.

Advanced Pointers Complex Types

Pointers to pointers, function pointers, void pointers for flexibility.

Smart Pointers Automatic Management

Automatically manage memory, prevent leaks (C++11 and later).

Pointer Arithmetic

Navigate through memory using arithmetic operations on pointers.

Pointer Concept Visualization
Variable
int x = 42;
Value: 42
Address: 0x7fff5fbff8ac
Pointer
int* ptr = &x;
Stores: 0x7fff5fbff8ac
Points to: 42

Pointer stores memory address: ptr contains the address of x, allowing indirect access to its value.

Key Concepts:
  • Address Operator (&): Gets memory address of a variable
  • Dereference Operator (*): Accesses value at a memory address
  • Null Pointer: Pointer that doesn't point to any valid location
  • Memory Address: Unique location in computer's memory
  • Pointer Size: Same for all data types (typically 4 or 8 bytes)

1. Basic Pointers

// Declaration syntax:
data_type* pointer_name;

// Examples:
int* ptr1; // Pointer to integer
float* ptr2; // Pointer to float
char* ptr3; // Pointer to character
double* ptr4; // Pointer to double

Declaration, Initialization, and Basic Operations

Basic Pointer Operations
#include <iostream>
using namespace std;

int main() {
    // Basic variable
    int number = 42;
    cout << "Variable 'number' value: " << number << endl;
    cout << "Variable 'number' address: " << &number << endl << endl;
    
    // 1. Pointer declaration and initialization
    int* ptr = &number;  // ptr stores address of number
    
    cout << "1. Pointer Basics:" << endl;
    cout << "Pointer 'ptr' value (address it stores): " << ptr << endl;
    cout << "Value at address stored in ptr (*ptr): " << *ptr << endl;
    cout << "Address of pointer itself (&ptr): " << &ptr << endl << endl;
    
    // 2. Modifying value through pointer
    cout << "2. Modifying through pointer:" << endl;
    cout << "Before: number = " << number << endl;
    *ptr = 100;  // Modify value at the address ptr points to
    cout << "After *ptr = 100: number = " << number << endl << endl;
    
    // 3. Pointer to different types
    cout << "3. Pointers to different data types:" << endl;
    
    float f = 3.14f;
    float* fptr = &f;
    cout << "Float pointer: f = " << *fptr << ", address = " << fptr << endl;
    
    char c = 'A';
    char* cptr = &c;
    cout << "Char pointer: c = '" << *cptr << "', address = " << (void*)cptr << endl;
    
    double d = 2.71828;
    double* dptr = &d;
    cout << "Double pointer: d = " << *dptr << ", address = " << dptr << endl << endl;
    
    // 4. Null pointer
    cout << "4. Null Pointers:" << endl;
    int* nullPtr = nullptr;  // Modern C++ (C++11 and later)
    int* nullPtr2 = NULL;    // Traditional C/C++
    int* nullPtr3 = 0;       // Alternative
    
    cout << "nullptr: " << nullPtr << endl;
    cout << "NULL: " << nullPtr2 << endl;
    cout << "0: " << nullPtr3 << endl;
    
    // Always check before dereferencing
    if (nullPtr == nullptr) {
        cout << "Pointer is null - safe to check!" << endl;
    }
    
    // 5. Pointer size (platform dependent)
    cout << "\n5. Pointer Sizes:" << endl;
    cout << "Size of int*: " << sizeof(int*) << " bytes" << endl;
    cout << "Size of float*: " << sizeof(float*) << " bytes" << endl;
    cout << "Size of char*: " << sizeof(char*) << " bytes" << endl;
    cout << "Size of double*: " << sizeof(double*) << " bytes" << endl;
    cout << "Size of void*: " << sizeof(void*) << " bytes" << endl << endl;
    
    // 6. Pointer reassignment
    cout << "6. Pointer Reassignment:" << endl;
    int x = 10, y = 20;
    int* p = &x;
    
    cout << "Initially p points to x: *p = " << *p << endl;
    p = &y;  // Now p points to y
    cout << "After p = &y: *p = " << *p << endl;
    
    // 7. Const pointers
    cout << "\n7. Const with Pointers:" << endl;
    
    // Pointer to const data
    const int constant = 100;
    const int* ptrToConst = &constant;
    cout << "Pointer to const: *ptrToConst = " << *ptrToConst << endl;
    // *ptrToConst = 200;  // Error: cannot modify const data through pointer
    
    // Const pointer (cannot point elsewhere)
    int value = 50;
    int* const constPtr = &value;
    *constPtr = 60;  // OK: can modify the data
    cout << "Const pointer: *constPtr = " << *constPtr << endl;
    // constPtr = &x;  // Error: cannot reassign const pointer
    
    // Const pointer to const data
    const int* const constPtrToConst = &constant;
    cout << "Const pointer to const: *constPtrToConst = " << *constPtrToConst << endl << endl;
    
    return 0;
}
Best Practices for Basic Pointers:
  • Always initialize pointers (use nullptr if not pointing to valid memory)
  • Check for null before dereferencing
  • Use const appropriately to prevent accidental modifications
  • Be aware of pointer sizes on different platforms (32-bit vs 64-bit)
  • Understand the difference between pointer value (address) and pointed value

2. Complete Pointer Types Comparison

The following table compares all pointer types in C++ with their syntax, use cases, and characteristics:

Pointer Type Syntax When to Use Characteristics
Basic Pointer int* ptr; Direct memory access, dynamic allocation Stores address, can be reassigned
Pointer to Pointer int** ptr; Dynamic 2D arrays, function arguments Points to another pointer, double indirection
Void Pointer (Generic) void* ptr; Generic functions, memory operations Can point to any type, cannot be dereferenced directly
Function Pointer int (*funcPtr)(int, int); Callbacks, event handlers, strategy pattern Points to function, enables runtime function selection
Array Pointer int (*arrPtr)[10]; Pointer to entire array, multi-dimensional arrays Different from pointer to first element
Const Pointer int* const ptr; Pointer that cannot be reassigned Constant pointer, variable data
Pointer to Const const int* ptr; Read-only access to data Variable pointer, constant data
unique_ptr (C++11) unique_ptr<int> ptr; Single ownership, resource management Automatically deletes, cannot be copied
shared_ptr (C++11) shared_ptr<int> ptr; Shared ownership, reference counting Automatically deletes when last reference goes
weak_ptr (C++11) weak_ptr<int> ptr; Break circular references Non-owning reference to shared_ptr
this Pointer this Within class methods to access members Implicit pointer to current object

3. Advanced Pointer Types

Pointer to Pointer (Double Pointer)

Pointer to Pointer Examples
#include <iostream>
using namespace std;

int main() {
    cout << "=== POINTER TO POINTER (DOUBLE POINTER) ===\n\n";
    
    // Single pointer
    int value = 42;
    int* ptr = &value;
    
    // Double pointer
    int** dptr = &ptr;
    
    cout << "1. Basic double pointer operations:" << endl;
    cout << "value = " << value << endl;
    cout << "&value = " << &value << endl;
    cout << "ptr = " << ptr << " (address of value)" << endl;
    cout << "*ptr = " << *ptr << " (value at address)" << endl;
    cout << "&ptr = " << &ptr << " (address of ptr)" << endl;
    cout << "dptr = " << dptr << " (address of ptr)" << endl;
    cout << "*dptr = " << *dptr << " (value at dptr = ptr = address of value)" << endl;
    cout << "**dptr = " << **dptr << " (value at *dptr = value)" << endl << endl;
    
    // 2. Modifying through double pointer
    cout << "2. Modifying through double pointer:" << endl;
    **dptr = 100;
    cout << "After **dptr = 100:" << endl;
    cout << "value = " << value << endl;
    cout << "*ptr = " << *ptr << endl;
    cout << "**dptr = " << **dptr << endl << endl;
    
    // 3. Dynamic 2D array using double pointers
    cout << "3. Dynamic 2D array (matrix):" << endl;
    int rows = 3, cols = 4;
    
    // Allocate array of pointers (rows)
    int** matrix = new int*[rows];
    
    // Allocate each row
    for (int i = 0; i < rows; i++) {
        matrix[i] = new int[cols];
    }
    
    // Initialize matrix
    int counter = 1;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = counter++;
        }
    }
    
    // Print matrix
    cout << "Matrix " << rows << "x" << cols << ":" << endl;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            cout << matrix[i][j] << "\t";
        }
        cout << endl;
    }
    
    // Deallocate memory
    for (int i = 0; i < rows; i++) {
        delete[] matrix[i];
    }
    delete[] matrix;
    
    // 4. Pointer to pointer in function arguments
    cout << "\n4. Pointer to pointer in functions:" << endl;
    
    void allocateMemory(int** ptr, int size);
    void printArray(int* arr, int size);
    
    int* dynamicArray = nullptr;
    allocateMemory(&dynamicArray, 5);  // Pass address of pointer
    
    if (dynamicArray) {
        for (int i = 0; i < 5; i++) {
            dynamicArray[i] = (i + 1) * 10;
        }
        
        cout << "Dynamic array: ";
        printArray(dynamicArray, 5);
        
        delete[] dynamicArray;
    }
    
    return 0;
}

void allocateMemory(int** ptr, int size) {
    *ptr = new int[size];  // Allocate memory and assign to *ptr
    cout << "Allocated " << size << " integers at address " << *ptr << endl;
}

void printArray(int* arr, int size) {
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

Void Pointers (Generic Pointers)

Void Pointer Examples
#include <iostream>
#include <cstring>
using namespace std;

int main() {
    cout << "=== VOID POINTERS (GENERIC POINTERS) ===\n\n";
    
    // 1. Basic void pointer operations
    cout << "1. Basic void pointer:" << endl;
    
    int intValue = 42;
    float floatValue = 3.14f;
    char charValue = 'A';
    
    void* voidPtr;
    
    // Point to integer
    voidPtr = &intValue;
    cout << "voidPtr points to int: " << *(int*)voidPtr << endl;
    
    // Point to float
    voidPtr = &floatValue;
    cout << "voidPtr points to float: " << *(float*)voidPtr << endl;
    
    // Point to char
    voidPtr = &charValue;
    cout << "voidPtr points to char: '" << *(char*)voidPtr << "'" << endl << endl;
    
    // 2. Memory operations with void pointers
    cout << "2. Memory operations:" << endl;
    
    // Allocate memory
    void* memory = malloc(100);  // Allocate 100 bytes
    if (memory) {
        cout << "Allocated 100 bytes at address: " << memory << endl;
        
        // Use as different types
        int* intArray = (int*)memory;
        for (int i = 0; i < 5; i++) {
            intArray[i] = i * 10;
        }
        
        cout << "As int array: ";
        for (int i = 0; i < 5; i++) {
            cout << intArray[i] << " ";
        }
        cout << endl;
        
        // Use as char array (string)
        char* str = (char*)memory;
        strcpy(str, "Hello");
        cout << "As string: " << str << endl;
        
        free(memory);
    }
    
    // 3. Generic function using void pointer
    cout << "\n3. Generic print function:" << endl;
    
    void printValue(void* data, char type) {
        switch (type) {
            case 'i':
                cout << "Integer: " << *(int*)data << endl;
                break;
            case 'f':
                cout << "Float: " << *(float*)data << endl;
                break;
            case 'c':
                cout << "Char: '" << *(char*)data << "'" << endl;
                break;
            case 'd':
                cout << "Double: " << *(double*)data << endl;
                break;
            default:
                cout << "Unknown type" << endl;
        }
    }
    
    int i = 100;
    float f = 2.718f;
    char c = 'Z';
    double d = 1.618;
    
    printValue(&i, 'i');
    printValue(&f, 'f');
    printValue(&c, 'c');
    printValue(&d, 'd');
    
    // 4. Array of void pointers
    cout << "\n4. Array of void pointers (heterogeneous collection):" << endl;
    
    void* mixedArray[4];
    mixedArray[0] = &i;
    mixedArray[1] = &f;
    mixedArray[2] = &c;
    mixedArray[3] = &d;
    
    char types[] = {'i', 'f', 'c', 'd'};
    for (int j = 0; j < 4; j++) {
        printValue(mixedArray[j], types[j]);
    }
    
    return 0;
}
Important Notes about Void Pointers:
  • Cannot be dereferenced directly (must be cast to appropriate type first)
  • No pointer arithmetic allowed on void pointers
  • Use with caution - type safety is the programmer's responsibility
  • Commonly used in low-level memory operations and generic programming
  • Modern C++ alternatives: templates, std::any (C++17)

4. Function Pointers

Function Pointer Examples
#include <iostream>
#include <cmath>
#include <functional>  // For std::function (C++11)
using namespace std;

// Function prototypes
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
float divide(int a, int b) { return (b != 0) ? (float)a / b : 0; }

// Function that takes function pointer as parameter
int operate(int x, int y, int (*operation)(int, int)) {
    return operation(x, y);
}

// Typedef for function pointer (makes syntax cleaner)
typedef int (*MathOperation)(int, int);

// Another way: using alias (C++11)
using MathFunc = int (*)(int, int);

int main() {
    cout << "=== FUNCTION POINTERS ===\n\n";
    
    // 1. Basic function pointer declaration and use
    cout << "1. Basic function pointers:" << endl;
    
    int (*funcPtr)(int, int);  // Declaration
    
    funcPtr = add;  // Point to add function
    cout << "add(10, 5) = " << funcPtr(10, 5) << endl;
    
    funcPtr = subtract;  // Point to subtract function
    cout << "subtract(10, 5) = " << funcPtr(10, 5) << endl;
    
    funcPtr = multiply;  // Point to multiply function
    cout << "multiply(10, 5) = " << funcPtr(10, 5) << endl << endl;
    
    // 2. Array of function pointers
    cout << "2. Array of function pointers:" << endl;
    
    MathOperation operations[] = {add, subtract, multiply};
    const char* operationNames[] = {"Addition", "Subtraction", "Multiplication"};
    
    for (int i = 0; i < 3; i++) {
        cout << operationNames[i] << ": " 
             << operations[i](10, 5) << endl;
    }
    cout << endl;
    
    // 3. Function pointer as parameter
    cout << "3. Function pointer as function parameter:" << endl;
    
    cout << "operate(10, 5, add) = " << operate(10, 5, add) << endl;
    cout << "operate(10, 5, subtract) = " << operate(10, 5, subtract) << endl;
    cout << "operate(10, 5, multiply) = " << operate(10, 5, multiply) << endl << endl;
    
    // 4. Callback function example
    cout << "4. Callback function example:" << endl;
    
    void processArray(int arr[], int size, int (*callback)(int)) {
        for (int i = 0; i < size; i++) {
            arr[i] = callback(arr[i]);
        }
    }
    
    int square(int x) { return x * x; }
    int cube(int x) { return x * x * x; }
    int doubleValue(int x) { return x * 2; }
    
    int numbers[] = {1, 2, 3, 4, 5};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    
    cout << "Original array: ";
    for (int i = 0; i < size; i++) cout << numbers[i] << " ";
    cout << endl;
    
    processArray(numbers, size, square);
    cout << "After squaring: ";
    for (int i = 0; i < size; i++) cout << numbers[i] << " ";
    cout << endl;
    
    // Reset array
    for (int i = 0; i < size; i++) numbers[i] = i + 1;
    
    processArray(numbers, size, cube);
    cout << "After cubing: ";
    for (int i = 0; i < size; i++) cout << numbers[i] << " ";
    cout << endl << endl;
    
    // 5. Comparison with std::function (C++11)
    cout << "5. std::function (C++11 alternative):" << endl;
    
    // std::function is more flexible
    std::function func;
    
    func = add;
    cout << "std::function add: " << func(10, 5) << endl;
    
    func = subtract;
    cout << "std::function subtract: " << func(10, 5) << endl;
    
    // Can also use lambda functions
    func = [](int a, int b) { return a * b; };
    cout << "std::function lambda: " << func(10, 5) << endl << endl;
    
    // 6. Member function pointers
    cout << "6. Member function pointers:" << endl;
    
    class Calculator {
    public:
        int add(int a, int b) { return a + b; }
        int multiply(int a, int b) { return a * b; }
        static int staticAdd(int a, int b) { return a + b; }
    };
    
    Calculator calc;
    
    // Non-static member function pointer
    int (Calculator::*memberFuncPtr)(int, int) = &Calculator::add;
    cout << "Member function add: " << (calc.*memberFuncPtr)(10, 5) << endl;
    
    // Static member function pointer (same as regular function pointer)
    int (*staticFuncPtr)(int, int) = &Calculator::staticAdd;
    cout << "Static member function add: " << staticFuncPtr(10, 5) << endl;
    
    return 0;
}
Function Pointer Applications:
  • Callbacks: Event handlers, GUI programming
  • Strategy Pattern: Select algorithm at runtime
  • Sorting: Custom comparison functions
  • Plugin Architecture: Dynamic function loading
  • State Machines: Different behaviors for states

5. Smart Pointers (C++11+)

// Smart pointer syntax:
#include <memory>

// unique_ptr (exclusive ownership):
std::unique_ptr<Type> ptr = std::make_unique<Type>(args);

// shared_ptr (shared ownership):
std::shared_ptr<Type> ptr = std::make_shared<Type>(args);

// weak_ptr (non-owning reference):
std::weak_ptr<Type> wptr = sharedPtr;
Smart Pointer Examples
#include <iostream>
#include <memory>  // For smart pointers
#include <vector>
using namespace std;

class Resource {
private:
    string name;
public:
    Resource(string n) : name(n) {
        cout << "Resource '" << name << "' created" << endl;
    }
    
    ~Resource() {
        cout << "Resource '" << name << "' destroyed" << endl;
    }
    
    void use() {
        cout << "Using resource '" << name << "'" << endl;
    }
    
    string getName() const { return name; }
};

int main() {
    cout << "=== SMART POINTERS (C++11 and later) ===\n\n";
    
    // 1. unique_ptr (exclusive ownership)
    cout << "1. unique_ptr (Exclusive Ownership):" << endl;
    cout << "-------------------------------------" << endl;
    
    {
        // Create unique_ptr using make_unique (C++14)
        unique_ptr res1 = make_unique("Unique Resource 1");
        res1->use();
        
        // unique_ptr cannot be copied
        // unique_ptr res2 = res1;  // Error: copy constructor deleted
        
        // But can be moved
        unique_ptr res2 = move(res1);
        if (!res1) {
            cout << "res1 is now empty after move" << endl;
        }
        if (res2) {
            cout << "res2 now owns: " << res2->getName() << endl;
        }
        
        // Array support
        unique_ptr arr = make_unique(3);
        cout << "Created array of 3 resources" << endl;
        
        // Custom deleter
        auto customDeleter = [](Resource* r) {
            cout << "Custom deleter called for: " << r->getName() << endl;
            delete r;
        };
        unique_ptr res3(new Resource("Custom Deleted"), customDeleter);
    }  // All unique_ptrs automatically destroyed here
    
    cout << endl;
    
    // 2. shared_ptr (shared ownership)
    cout << "2. shared_ptr (Shared Ownership):" << endl;
    cout << "----------------------------------" << endl;
    
    {
        // Create shared_ptr using make_shared (more efficient)
        shared_ptr shared1 = make_shared("Shared Resource 1");
        cout << "Use count: " << shared1.use_count() << endl;
        
        // Create another shared_ptr pointing to same resource
        shared_ptr shared2 = shared1;
        cout << "After sharing, use count: " << shared1.use_count() << endl;
        
        shared1->use();
        shared2->use();
        
        // Create more references
        {
            shared_ptr shared3 = shared2;
            cout << "In inner scope, use count: " << shared1.use_count() << endl;
        }  // shared3 destroyed
        
        cout << "After inner scope, use count: " << shared1.use_count() << endl;
        
        // Reset shared_ptr
        shared1.reset();
        cout << "After reset shared1, use count: " << shared2.use_count() << endl;
        
        if (!shared1) {
            cout << "shared1 is now empty" << endl;
        }
    }  // Last shared_ptr destroyed, resource freed
    
    cout << endl;
    
    // 3. weak_ptr (non-owning reference)
    cout << "3. weak_ptr (Non-owning Reference):" << endl;
    cout << "------------------------------------" << endl;
    
    {
        shared_ptr shared = make_shared("Resource for weak_ptr");
        weak_ptr weak = shared;
        
        cout << "shared use count: " << shared.use_count() << endl;
        
        // Convert weak_ptr to shared_ptr to access resource
        if (auto locked = weak.lock()) {
            cout << "Resource accessed via weak_ptr: " << locked->getName() << endl;
            cout << "Now use count: " << shared.use_count() << endl;
        }
        
        // Reset shared_ptr
        shared.reset();
        cout << "After reset shared, use count: " << shared.use_count() << endl;
        
        // Check if weak_ptr is expired
        if (weak.expired()) {
            cout << "weak_ptr is expired (resource destroyed)" << endl;
        }
        
        // Trying to lock expired weak_ptr
        auto locked = weak.lock();
        if (!locked) {
            cout << "Failed to lock expired weak_ptr" << endl;
        }
    }
    
    cout << endl;
    
    // 4. Circular reference problem and solution with weak_ptr
    cout << "4. Circular Reference Problem:" << endl;
    cout << "--------------------------------" << endl;
    
    class Node {
    public:
        string name;
        shared_ptr partner;  // Problem: circular reference
        
        Node(string n) : name(n) {
            cout << "Node '" << name << "' created" << endl;
        }
        
        ~Node() {
            cout << "Node '" << name << "' destroyed" << endl;
        }
    };
    
    class SafeNode {
    public:
        string name;
        weak_ptr partner;  // Solution: use weak_ptr
        
        SafeNode(string n) : name(n) {
            cout << "SafeNode '" << name << "' created" << endl;
        }
        
        ~SafeNode() {
            cout << "SafeNode '" << name << "' destroyed" << endl;
        }
    };
    
    cout << "\nProblem (circular reference with shared_ptr):" << endl;
    {
        auto nodeA = make_shared("A");
        auto nodeB = make_shared("B");
        
        nodeA->partner = nodeB;
        nodeB->partner = nodeA;
        
        cout << "Use count A: " << nodeA.use_count() << endl;
        cout << "Use count B: " << nodeB.use_count() << endl;
        // Memory leak! Nodes won't be destroyed automatically
    }
    
    cout << "\nSolution (using weak_ptr to break circular reference):" << endl;
    {
        auto nodeA = make_shared("A");
        auto nodeB = make_shared("B");
        
        nodeA->partner = nodeB;
        nodeB->partner = nodeA;
        
        cout << "Use count A: " << nodeA.use_count() << endl;
        cout << "Use count B: " << nodeB.use_count() << endl;
        // No memory leak! Nodes will be destroyed properly
    }
    
    // 5. Practical example: Resource manager
    cout << "\n5. Practical Example: Resource Manager" << endl;
    cout << "----------------------------------------" << endl;
    
    class Texture {
    private:
        string filename;
    public:
        Texture(string fname) : filename(fname) {
            cout << "Loading texture: " << filename << endl;
        }
        
        ~Texture() {
            cout << "Unloading texture: " << filename << endl;
        }
        
        void render() {
            cout << "Rendering texture: " << filename << endl;
        }
    };
    
    class TextureManager {
    private:
        vector> textures;
        
    public:
        shared_ptr loadTexture(string filename) {
            // Check if already loaded
            for (auto& tex : textures) {
                // In real implementation, compare filenames
            }
            
            // Load new texture
            auto texture = make_shared(filename);
            textures.push_back(texture);
            return texture;
        }
        
        void listTextures() {
            cout << "Loaded textures: " << textures.size() << endl;
        }
    };
    
    TextureManager manager;
    auto tex1 = manager.loadTexture("wall.png");
    auto tex2 = manager.loadTexture("floor.png");
    auto tex3 = manager.loadTexture("ceiling.png");
    
    // Multiple objects can share same texture
    auto tex4 = tex1;  // Share wall texture
    
    tex1->render();
    tex2->render();
    
    manager.listTextures();
    
    return 0;
}
unique_ptr
  • Ownership: Exclusive
  • Copyable: No
  • Movable: Yes
  • Use when: Single owner needed
  • Overhead: Minimal
shared_ptr
  • Ownership: Shared
  • Copyable: Yes
  • Movable: Yes
  • Use when: Multiple owners needed
  • Overhead: Reference counting
weak_ptr
  • Ownership: None
  • Copyable: Yes
  • Movable: Yes
  • Use when: Break circular references
  • Overhead: Minimal

6. Pointer Arithmetic

Pointer Arithmetic Examples
#include <iostream>
using namespace std;

int main() {
    cout << "=== POINTER ARITHMETIC ===\n\n";
    
    // 1. Basic pointer arithmetic
    cout << "1. Basic Pointer Arithmetic:" << endl;
    cout << "------------------------------" << endl;
    
    int arr[] = {10, 20, 30, 40, 50};
    int* ptr = arr;  // Points to first element
    
    cout << "Array elements: ";
    for (int i = 0; i < 5; i++) {
        cout << arr[i] << " ";
    }
    cout << endl << endl;
    
    cout << "Pointer initially points to: arr[0] = " << *ptr << endl;
    cout << "Pointer address: " << ptr << endl;
    
    // Increment pointer (moves to next element)
    ptr++;
    cout << "\nAfter ptr++:" << endl;
    cout << "Points to: arr[1] = " << *ptr << endl;
    cout << "Pointer address: " << ptr << endl;
    cout << "Address difference: " << (ptr - arr) * sizeof(int) << " bytes" << endl;
    
    // Decrement pointer
    ptr--;
    cout << "\nAfter ptr-- (back to start):" << endl;
    cout << "Points to: arr[0] = " << *ptr << endl;
    
    // Add integer to pointer
    ptr = ptr + 3;
    cout << "\nAfter ptr = ptr + 3:" << endl;
    cout << "Points to: arr[3] = " << *ptr << endl;
    
    // Subtract integer from pointer
    ptr = ptr - 2;
    cout << "\nAfter ptr = ptr - 2:" << endl;
    cout << "Points to: arr[1] = " << *ptr << endl;
    
    // Pointer difference
    int* ptr2 = &arr[4];
    cout << "\nPointer difference (ptr2 - ptr):" << endl;
    cout << "ptr points to arr[1] = " << *ptr << endl;
    cout << "ptr2 points to arr[4] = " << *ptr2 << endl;
    cout << "ptr2 - ptr = " << (ptr2 - ptr) << " elements" << endl;
    cout << "In bytes: " << (ptr2 - ptr) * sizeof(int) << " bytes" << endl << endl;
    
    // 2. Array traversal using pointer arithmetic
    cout << "2. Array Traversal with Pointers:" << endl;
    cout << "----------------------------------" << endl;
    
    double numbers[] = {1.1, 2.2, 3.3, 4.4, 5.5};
    double* numPtr = numbers;
    int numCount = sizeof(numbers) / sizeof(numbers[0]);
    
    cout << "Array traversal:" << endl;
    for (int i = 0; i < numCount; i++) {
        cout << "numbers[" << i << "] = " << *(numPtr + i) 
             << " at address " << (numPtr + i) << endl;
    }
    cout << endl;
    
    // 3. Pointer arithmetic with different data types
    cout << "3. Different Data Types:" << endl;
    cout << "-------------------------" << endl;
    
    char chars[] = {'A', 'B', 'C', 'D', 'E'};
    char* charPtr = chars;
    
    cout << "Char array (1 byte each):" << endl;
    cout << "charPtr = " << (void*)charPtr << endl;
    cout << "charPtr + 1 = " << (void*)(charPtr + 1) << endl;
    cout << "Difference: " << (charPtr + 1) - charPtr << " byte" << endl << endl;
    
    int ints[] = {1, 2, 3, 4, 5};
    int* intPtr = ints;
    
    cout << "Int array (typically 4 bytes each):" << endl;
    cout << "intPtr = " << intPtr << endl;
    cout << "intPtr + 1 = " << intPtr + 1 << endl;
    cout << "Difference: " << (intPtr + 1) - intPtr << " elements" << endl;
    cout << "Byte difference: " << (char*)(intPtr + 1) - (char*)intPtr << " bytes" << endl << endl;
    
    // 4. Pointer arithmetic in strings
    cout << "4. String Manipulation:" << endl;
    cout << "------------------------" << endl;
    
    char str[] = "Hello, World!";
    char* strPtr = str;
    
    cout << "Original string: " << str << endl;
    
    // Find length using pointers
    char* endPtr = strPtr;
    while (*endPtr != '\0') {
        endPtr++;
    }
    cout << "String length (pointer arithmetic): " << (endPtr - strPtr) << endl;
    
    // Reverse string using pointers
    char* start = str;
    char* end = str;
    while (*end) end++;  // Move to null terminator
    end--;  // Move back to last character
    
    cout << "Reversed string: ";
    while (end >= start) {
        cout << *end;
        end--;
    }
    cout << endl << endl;
    
    // 5. Pointer arithmetic with 2D arrays
    cout << "5. 2D Arrays and Pointers:" << endl;
    cout << "---------------------------" << endl;
    
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    
    // Pointer to first element of 2D array
    int* flatPtr = &matrix[0][0];
    
    cout << "2D array as flat memory:" << endl;
    for (int i = 0; i < 3 * 4; i++) {
        cout << *(flatPtr + i) << " ";
        if ((i + 1) % 4 == 0) cout << endl;
    }
    cout << endl;
    
    // 6. Pointer comparison
    cout << "6. Pointer Comparison:" << endl;
    cout << "----------------------" << endl;
    
    int values[] = {10, 20, 30, 40, 50};
    int* p1 = &values[1];
    int* p2 = &values[3];
    
    cout << "p1 points to: " << *p1 << " at " << p1 << endl;
    cout << "p2 points to: " << *p2 << " at " << p2 << endl;
    
    if (p1 < p2) {
        cout << "p1 comes before p2 in memory" << endl;
    }
    
    if (p1 != p2) {
        cout << "p1 and p2 point to different locations" << endl;
    }
    
    // 7. Bounds checking with pointer arithmetic
    cout << "\n7. Bounds Checking:" << endl;
    cout << "-------------------" << endl;
    
    int buffer[10] = {0};
    int* bufPtr = buffer;
    int* bufEnd = buffer + 10;  // One past the end
    
    // Safe iteration
    for (int* p = buffer; p < bufEnd; p++) {
        *p = (p - buffer) * 10;  // Calculate index using pointer arithmetic
    }
    
    cout << "Buffer after filling: ";
    for (int i = 0; i < 10; i++) {
        cout << buffer[i] << " ";
    }
    cout << endl;
    
    return 0;
}
Pointer Arithmetic Rules:
  • Adding n to a pointer moves it forward by n * sizeof(type) bytes
  • Subtracting n from a pointer moves it backward by n * sizeof(type) bytes
  • Pointer subtraction gives the number of elements between them
  • Pointer arithmetic only works within the same array or allocated memory block
  • Comparing pointers is only valid when they point to the same array

7. Best Practices & Common Pitfalls

Common Error Example Solution
Dereferencing Null Pointer int* ptr = nullptr; *ptr = 5; Always check for null before dereferencing
Dangling Pointer int* ptr = new int; delete ptr; *ptr = 10; Set pointer to null after deletion, use smart pointers
Memory Leak int* ptr = new int[100]; // No delete Always match new with delete, use RAII/smart pointers
Buffer Overflow int arr[5]; int* p = arr + 10; Check bounds, use containers like vector
Type Mismatch float* ptr = (float*)&intVar; Avoid unsafe casts, use proper type conversions
Pointer Arithmetic on Wrong Type void* ptr; ptr++; Don't use arithmetic on void pointers
Returning Local Variable Address int* func() { int x; return &x; } Don't return address of local variables
Double Free delete ptr; delete ptr; Set pointer to null after deletion
Best Practices
  • Always initialize pointers (use nullptr for empty)
  • Prefer smart pointers over raw pointers for ownership
  • Use const whenever possible
  • Check pointer validity before dereferencing
  • Match every new with exactly one delete
  • Use range-based for loops or iterators when possible
  • Document pointer ownership responsibilities
Performance Tips
  • Minimize pointer indirection (deep nesting hurts cache)
  • Use local pointers for frequently accessed data
  • Avoid unnecessary pointer arithmetic in tight loops
  • Consider reference types when ownership isn't needed
  • Profile before optimizing pointer usage

Modern C++ Pointer Guidelines

In modern C++ (C++11 and later), prefer smart pointers (unique_ptr, shared_ptr) for ownership, use raw pointers only for non-owning references, and leverage references when null isn't needed. Use std::array or std::vector instead of raw arrays.

Quick Quiz: Test Your Knowledge

What will be the output of this code?

int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr + 2;
cout << *(ptr - 1) + *(ptr + 1);
A) 6
B) 7
C) 8
D) Segmentation fault

Summary & Key Takeaways

Pointer Fundamentals
  • Pointers store memory addresses
  • Use & to get address, * to dereference
  • Always initialize pointers
  • Check for null before dereferencing
  • Understand pointer size and platform differences
Smart Pointers (Modern C++)
  • unique_ptr: Exclusive ownership, cannot be copied
  • shared_ptr: Shared ownership with reference counting
  • weak_ptr: Non-owning reference to break circular dependencies
  • Prefer smart pointers for automatic memory management
  • Use make_unique and make_shared for creation
Advanced Pointer Types
  • Pointer to pointer: For multi-dimensional dynamic arrays
  • Void pointer: Generic pointers, requires explicit casting
  • Function pointer: Enables callbacks and runtime polymorphism
  • this pointer: Implicit pointer to current object
  • Const pointers: Various combinations for safety
Pointer Arithmetic
  • Adding n moves pointer by n * sizeof(type)
  • Use for array traversal and string manipulation
  • Pointer subtraction gives element count difference
  • Only valid within same allocated memory block
  • Essential for low-level memory operations

When to Use Different Pointer Types

  • Raw pointers: Non-owning references, legacy code, low-level operations
  • Smart pointers: Memory ownership, automatic cleanup, modern C++ code
  • References: When null isn't needed, function parameters, return values
  • Function pointers: Callbacks, strategy pattern, event systems
  • Avoid: Raw pointers for ownership, manual memory management in modern code

Next Steps

Mastering pointers is crucial for advanced C++ programming. Practice with pointer-based data structures (linked lists, trees), explore memory management patterns, learn about move semantics (C++11), and study the Standard Template Library (STL) containers that internally use pointers. Understanding pointers deeply will make you a more effective C++ programmer.