It is well known the example to RAII a FILE*
using std::unique_ptr
:
struct FILEDeleter
{
typedef FILE *pointer;
void operator()(FILE *fp) { fclose(fp); }
};
typedef std::unique_ptr<FILE, FILEDeleter> FilePtr;
FilePtr f(fopen("file.txt", "r"));
Alas, a similar approach to POSIX close()
to RAII a file descriptor is not possible:
struct FDDeleter
{
typedef int pointer;
void operator()(int fd) { close(fp); }
};
typedef std::unique_ptr<int, FDDeleter> FD;
Although some compilers will work just fine, it is not valid because the fd==0
is a valid file descriptor! The null one should be -1
. But anyway, even if it were 0
it is still not valid, because FDDeleter::pointer
shall satisfy the requirements of NullablePointer (summing up):
- It shall be comparable to
nullptr
.
- It shall be value-initialized to a value that compares equal to
nullptr
.
Thus, UniqueHandle
is born!
#include <memory>
template <typename T, T TNul = T()>
class UniqueHandle
{
public:
UniqueHandle(std::nullptr_t = nullptr)
:m_id(TNul)
{ }
UniqueHandle(T x)
:m_id(x)
{ }
explicit operator bool() const { return m_id != TNul; }
operator T&() { return m_id; }
operator T() const { return m_id; }
T *operator&() { return &m_id; }
const T *operator&() const { return &m_id; }
friend bool operator == (UniqueHandle a, UniqueHandle b) { return a.m_id == b.m_id; }
friend bool operator != (UniqueHandle a, UniqueHandle b) { return a.m_id != b.m_id; }
friend bool operator == (UniqueHandle a, std::nullptr_t) { return a.m_id == TNul; }
friend bool operator != (UniqueHandle a, std::nullptr_t) { return a.m_id != TNul; }
friend bool operator == (std::nullptr_t, UniqueHandle b) { return TNul == b.m_id; }
friend bool operator != (std::nullptr_t, UniqueHandle b) { return TNul != b.m_id; }
private:
T m_id;
};
Its use is pretty easy, best seen with an example:
struct FDDeleter
{
typedef UniqueHandle<int, -1> pointer;
void operator()(pointer p)
{
close(p);
}
};
typedef std::unique_ptr<int, FDDeleter> FD;
FD fd(open("test.txt", O_RDONLY));
If you truly want a one-liner you could go with this generalization:
template <typename T, T TNul = T(), typename RD, RD (*D)(T)>
struct OLDeleter
{
typedef UniqueHandle<T, TNul> pointer;
void operator()(pointer p)
{
D(p);
}
};
And then just one line:
std::unique_ptr<int, OLDeleter<int, -1, int, close> > FD fd(open("test.txt", O_RDONLY));
The problem is that you must add the return of close()
as a template argument and assume that there isn't anything funny about this function that prevents its conversion to a int(*)(int)
(weird calling conventions, extra parameters, macros...) and that is quite inconvenient.
You could add a function wrapper:
void my_close(int fd) { close(fd); }
But if you are into it, you could as well write the whole struct FDDeleter
.