I couldn't understand, if there is already a default deleter in unique_ptr
, then what is the need of using a custom deleter?
Could anyone explain this by giving a simple example?
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:
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:
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