Home > Back-end >  When we go for custom deleter instead of default deleter in case of unique_ptr?
When we go for custom deleter instead of default deleter in case of unique_ptr?

Time:01-27

As told above I couldn't understand if there is already default deleter then what is the need of custom deleter in case of unique_ptr ?
Could anyone explain this by giving simple example?

CodePudding user response:

Here is an example of using std::unique_ptr to manage an old C style FILE* in RAII style.
The deleter will close the file automatically when the std::unique_ptr will attempt to release the managed resource.
This is useful e.g. if you have some legacy code using FILE* file(s) and you would like to avoid manually managing closing them.

#include <iostream>
#include <memory>

struct FileDeleter
{
    void operator()(FILE* pFile)
    {
        if (pFile)
        {
            std::cout << "closing file ...\n";
            fclose(pFile);
        }
    }
};

using FileUniquePtr = std::unique_ptr<FILE, FileDeleter>;

int main()
{
    {
        // Getting a FILE* from legacy code:
        FILE* pFile = nullptr;
        std::cout << "opening file ...\n";
        auto err = fopen_s(&pFile, "aaa.txt", "r");
        if (err != 0)
        {
            // Handle error ...
            return -1;
        }
        
        // New C   code:
        FileUniquePtr filePtr{ pFile };
        // Do something with the file via filePtr ...

    } // Here filePtr goes out of scope and the deleter will be called and close the file automatically.
}

Output:

opening file ...
closing file ...

CodePudding user response:

I use custom deletes to quickly wrap C-API, for example to help me with OpenSSL:

namespace helper {
template<typename T>
struct Deleter;

template<>
struct Deleter<::BIO>
{
    void operator()(::BIO* p) const
    {
        // result used during debugging
        [[maybe_unused]] auto result = BIO_free(p);
    }
};

template<>
struct Deleter<::X509>
{
    void operator()(::X509* p) const
    {
        X509_free(p);
    }
};

template<>
struct Deleter<::PKCS12>
{
    void operator()(::PKCS12* p) const
    {
        PKCS12_free(p);
    }
};

template<>
struct Deleter<::EVP_PKEY>
{
    void operator()(::EVP_PKEY* p) const
    {
        EVP_PKEY_free(p);
    }
};

template<>
struct Deleter<STACK_OF(X509)>
{
    void operator()(STACK_OF(X509) * p) const
    {
        sk_X509_pop_free(p, X509_free);
    }
};

template<>
struct Deleter<STACK_OF(GENERAL_NAME)>
{
    void operator()(STACK_OF(GENERAL_NAME) * p) const
    {
        sk_GENERAL_NAME_free(p);
    }
};

template<>
struct Deleter<GENERAL_NAME>
{
    void operator()(GENERAL_NAME* p) const
    {
        GENERAL_NAME_free(p);
    }
};

template<typename T, typename D = Deleter<T>>
std::unique_ptr<T, D> wrapUnique(T* p, D deleter = {})
{
    return std::unique_ptr<T, D>{p, deleter};
}
}

And I use other helper functions to make use OpenSSL more C sy. This is especially handy when handling errors (since release of resources is painless in such case).

CodePudding user response:

The default deleter is ok if doing delete over the pointer wrapped by unique_ptr is the correct thing to do to dispose of it. That is ok if your pointer comes from new, but it's not correct in many other situations.

A simple example is FILE *: a FILE * is a pointer that cannot be deleted with delete, but instead you have to use fclose on it. Using a custom deleter it's very easy to wrap it in a unique_ptr and let it take care of destruction/move/...:

namespace detail {
    struct file_ptr_deleter {
        void operator() (FILE *fp) {
            if(fp) fclose(fp);
        }
    };
}

/// RAII-style wrapper for a FILE*
struct unique_c_file : std::unique_ptr<FILE, detail::file_ptr_deleter> {
    using std::unique_ptr<FILE, detail::file_ptr_deleter>::unique_ptr;
    operator FILE *() { return get(); }
};

(in this case I even inherited from std::unique_ptr to ease the use of unique_c_file directly in C APIs).

Other, possibly more common cases, are if you have memory that comes from libraries, that provide their own function to delete it; for example, you may use BSD sockets, where getaddrinfo provides you an addrinfo * that must be freed using freeaddrinfo; even in this case, it's easy to create a smart pointer for that:

namespace detail {
    struct addrinfo_deleter {
        void operator()(addrinfo *elem) {
            if(elem) freeaddrinfo(elem);
        }
    };
}

/// unique_ptr to handle an            
  • Related