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);
}












Thursday, August 23, 2018

Understanding C++ Type Names with a Handy Function

C++ is a strongly-typed language. This means that every variable and expression has a type, and that type is known at compile-time. There are many times when we need to know the type of a variable or expression, especially when debugging or dealing with templates.

The C++ standard provides the typeid operator for this purpose. It gives us the type of an expression in the form of a std::type_info object, but it might not always give us the type in a human-readable format. For example, for the type int, it might return i instead of int.

To solve this problem, we can use a handy function, type_name(), which takes a template parameter T and returns a string containing the human-readable name of T. The code snippet above presents a definition of this function. Let's break it down:

Understanding the type_name() Function

The function type_name() is a template function that accepts a type T and returns its name as a std::string.

template <class T>
std::string type_name()
{
   //...
}

It first removes any reference qualifiers from T using std::remove_reference<T>::type:
typedef typename std::remove_reference<T>::type TR;

Then it demangles the name of TR using abi::__cxa_demangle if available. The function then constructs a string r with the demangled name, or typeid(TR).name() if demangling is not available.

Finally, it appends qualifiers such as const, volatile, and reference symbols (& or &&), if they exist:

#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
};


Using the type_name() Function

The type_name() function can be used like the following.

In this code, decltype is used to fetch the type of a variable or expression, and type_name() is used to print its human-readable name.


class Widget {
   public:
        int x, y;
};

auto f()
{
     int a = 9;
     cout << type_name<decltype(a)>() << endl;
     cout << type_name<decltype((a))>() << endl;
     return a;
}

int main(void)
{   
    Widget w;
    Widget &rw = w;
    auto aw = rw;
    auto daw = rw;
    cout << type_name<decltype(aw)>() << endl;
    cout << type_name<decltype(daw)>() << endl;
    f();
    return 0;
}

Running this code will output:

Widget
Widget&
int
int&

This output shows the type names of the variables aw, daw, a, and (a), respectively.

In conclusion, the type_name() function is a helpful tool for debugging and understanding C++ type information. It provides a human-readable type name and handles const, volatile, and reference qualifiers. It's a great addition to any C++ developer's toolkit.