Wednesday, August 29, 2018

Understanding Smart Pointers and Custom Deleters in C++


In C++, smart pointers are a feature of the Standard Template Library (STL) that provide the functionality of pointers while also managing memory. They automatically deallocate memory when they're no longer needed, preventing memory leaks. Two common types of smart pointers are std::unique_ptr and std::shared_ptr.

In this article, we will delve into these two types of smart pointers, with a focus on the unique feature of custom deleters, using an investment management example.

Unique Pointers

The std::unique_ptr is a smart pointer that exclusively owns and manages another object through a pointer and disposes of that object when the unique_ptr is destroyed. A unique_ptr cannot be copied, but it can be moved to transfer ownership of the managed object.

In the context of our example, we have created several classes representing different types of investments (Investment, Stock, Bond, and RealEstate).

We have also defined lambda functions CancelInvestment, CancelStock, CancelBond, and CancelRealEstate as custom deleters.
In the uniqueMakeInvestment function, we create a unique_ptr that uses a custom deleter (CancelInvestment) to manage instances of Investment classes.

A unique_ptr with a custom deleter has the type of the deleter as part of its type. It's important to note that the deleter does not change the size of the unique_ptr.

Shared Pointers

A std::shared_ptr is a smart pointer that retains shared ownership of an object. Multiple shared_ptr pointers can point to the same object, and the object is destroyed when the last shared_ptr is destroyed or goes out of scope.

The sharedMakeInvestment function is similar to the uniqueMakeInvestment function, but it uses shared_ptr instead of unique_ptr.

However, unlike unique_ptr, the custom deleter is not part of the type of shared_ptr. This allows more flexibility, as different shared_ptr instances managing the same object can have different custom deleters.

In the main function, both types of smart pointers are used:

In this way, the C++ language provides powerful tools for managing memory and object lifetimes with smart pointers, and even allows customization of the deletion process with custom deleters. This is particularly useful in complex applications where memory management is crucial for performance and stability.

#include<iostream>
#include<memory>

using namespace std;

class Investment {
    public:
        Investment() { cout << "Investment( " << this << ")" <<endl; }
        virtual void Type() const { cout << "Investment Type " << endl; }
        virtual ~Investment() { cout << "~Investment( " << this << ")" <<endl; }

};    
class Stock:public Investment {
    public:
        Stock() { cout << "Stock( " << this << ")" <<endl; }
        virtual void Type() const override { cout << "Stock Type " << endl; }
        virtual ~Stock() { cout << "~Stock( " << this << ")" <<endl; }
};

class Bond:public Investment {
    public:
        Bond() { cout << "Bond( " << this << ")" <<endl; }
        virtual void Type() const override { cout << "Bond Type " << endl; }
        virtual ~Bond() { cout << "~Bond( " << this << ")" <<endl; }
};

class RealEstate: public Investment {
    public:
        RealEstate() { cout << "RealEstate( " << this << ")" <<endl; }
        virtual void Type() const override{ cout << "RealEstate Type " << endl; }
        virtual ~RealEstate() { cout << "~RealEstate( " << this << ")" <<endl; }
};

auto CancelInvestment = [] (Investment *iv) { cout << "Transfer Investment: (" << iv << ")"  << endl; 
    delete iv;
};
auto CancelStock = [] (Investment *iv) { cout << "Sale Stock: (" << iv << ")"  << endl; 
    delete iv;
};
auto CancelBond = [] (Investment *iv) { cout << "Sale Bond: (" << iv << ")"  << endl; 
    delete iv;
};
auto CancelRealEstate = [] (Investment *iv) { cout << " Rent RealEstate: (" << iv << ")"  << endl; 
    delete iv;
};

template<typename T>
unique_ptr<Investment, decltype(CancelInvestment)>  uniqueMakeInvestment( const T type)
{
    switch (type)
    {
        case 1: {
                    unique_ptr<Investment, decltype(CancelInvestment)>Inv(new Stock, CancelInvestment);
                    return Inv;
                }
        case 2: {
                    unique_ptr<Investment, decltype(CancelInvestment)> Inv(new Bond, CancelInvestment);
                    return Inv;
                }
        case 3: {
                    unique_ptr<Investment, decltype(CancelInvestment)> Inv(new RealEstate, CancelInvestment);
                    return Inv;
                }
        default: {
                     unique_ptr<Investment, decltype(CancelInvestment)> Inv(new Investment, CancelInvestment);
                     return Inv;
                 }
    }
}



template<typename T>
shared_ptr<Investment> sharedMakeInvestment( const T type)
{
    switch (type)
    {
        case 1: {
                    shared_ptr<Investment>Inv(new Stock, CancelStock);
                    return Inv;
                }
        case 2: {
                    shared_ptr<Investment> Inv(new Bond, CancelBond);
                    return Inv;
                }
        case 3: {
                    shared_ptr<Investment> Inv(new RealEstate, CancelRealEstate);
                    return Inv;
                }
        default: {
                     shared_ptr<Investment> Inv(new Investment, CancelInvestment);
                     return Inv;
                 }
    }
}


int main(void)
{
    auto ptrA = move(uniqueMakeInvestment<int>(1));
    auto ptrB = sharedMakeInvestment<int>(3);
}












No comments:

Post a Comment