Smart Pointer

A smart pointer is a class template that provides automatic memory management for dynamically allocated objects. It helps prevent memory leaks by automatically deallocating the memory when it’s no longer needed. The two commonly used smart pointers in C++ are std::shared_ptr and std::unique_ptr. Let’s explore each one with an example and a graphic representation.

  1. std::unique_ptr:
    • std::unique_ptr is a smart pointer that exclusively owns the dynamically allocated object and ensures its deletion when it goes out of scope.
    • It cannot be copied but can be moved.
    • Here’s an example that demonstrates the usage of std::unique_ptr:
#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() {
        std::cout << "MyClass created" << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass destroyed" << std::endl;
    }
    void someMethod() {
        std::cout << "Some method called" << std::endl;
    }
};

int main() {
    std::cout << "Creating unique_ptr" << std::endl;
    std::unique_ptr<MyClass> ptr(new MyClass());
    
    std::cout << "Accessing object through unique_ptr" << std::endl;
    ptr->someMethod();
    
    std::cout << "unique_ptr goes out of scope" << std::endl;
    return 0;
}
  • Graphic representation:
      std::unique_ptr
+------------------------+
|   - pointer (MyClass*) |
|   - delete (MyClass*)  |
+------------------------+
            |
            v
      +------------------+
      |    MyClass       |
      +------------------+
  1. std::shared_ptr:
    • std::shared_ptr is a smart pointer that allows multiple pointers to share ownership of the dynamically allocated object.
    • It keeps a reference count and deletes the object only when the last shared_ptr pointing to it goes out of scope.
    • It can be copied and moved.
    • Here’s an example that demonstrates the usage of std::shared_ptr:
#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() {
        std::cout << "MyClass created" << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass destroyed" << std::endl;
    }
    void someMethod() {
        std::cout << "Some method called" << std::endl;
    }
};

int main() {
    std::cout << "Creating shared_ptr" << std::endl;
    std::shared_ptr<MyClass> ptr1(new MyClass());
    
    std::cout << "Copying shared_ptr" << std::endl;
    std::shared_ptr<MyClass> ptr2 = ptr1;
    
    std::cout << "Accessing object through shared_ptr" << std::endl;
    ptr1->someMethod();
    ptr2->someMethod();
    
    std::cout << "shared_ptr goes out of scope" << std::endl;
    return 0;
}
  • Graphic representation:
   std::shared_ptr
+------------------------+
|   - pointer (MyClass*) |
|   - ref count (2)      |
|   - delete (MyClass*)  |
+------------------------+
            |
            v
      +------------------+
      |    MyClass       |
      +------------------+
  • We can also create our own smart pointer, Here’s an example of how you can create a simple smart pointer implementation in C++:
#include <iostream>

template<typename T>
class SmartPointer {
public:
    SmartPointer(T* ptr) : rawPtr(ptr), refCount(new int(1)) {}

    // Copy constructor
    SmartPointer(const SmartPointer<T>& other) : rawPtr(other.rawPtr), refCount(other.refCount) {
        (*refCount)++;
    }

    // Destructor
    ~SmartPointer() {
        (*refCount)--;
        if (*refCount == 0) {
            delete rawPtr;
            delete refCount;
            std::cout << "Memory freed!" << std::endl;
        }
    }

    // Assignment operator
    SmartPointer<T>& operator=(const SmartPointer<T>& other) {
        if (this != &other) {
            (*refCount)--;
            if (*refCount == 0) {
                delete rawPtr;
                delete refCount;
            }

            rawPtr = other.rawPtr;
            refCount = other.refCount;
            (*refCount)++;
        }
        return *this;
    }

    T& operator*() const {
        return *rawPtr;
    }

    T* operator->() const {
        return rawPtr;
    }

private:
    T* rawPtr;
    int* refCount;
};

Now, let’s see an example usage of the SmartPointer class:

class MyClass {
public:
    MyClass(int data) : mData(data) {}

    void printData() {
        std::cout << "Data: " << mData << std::endl;
    }

private:
    int mData;
};

int main() {
    SmartPointer<MyClass> ptr1(new MyClass(42));
    SmartPointer<MyClass> ptr2 = ptr1;

    ptr1->printData();  // Output: Data: 42
    ptr2->printData();  // Output: Data: 42

    (*ptr1).printData();  // Output: Data: 42
    (*ptr2).printData();  // Output: Data: 42

    return 0;
}

In this example, the SmartPointer class manages the lifetime of the MyClass object. When ptr1 is assigned to ptr2, the reference count is incremented. When either ptr1 or ptr2 goes out of scope, the destructor decreases the reference count, and if the count reaches zero, the memory is freed.

Note that this is a simplified implementation for demonstration purposes. A complete smart pointer implementation should consider more aspects, such as handling move semantics, implementing the rule of three/five, and using atomic operations for thread safety in concurrent environments.

Leave a Reply

Your email address will not be published. Required fields are marked *