|
Boost : |
From: Ulrich Eckhardt (doomster_at_[hidden])
Date: 2007-05-27 10:37:50
Hi!
I have a library here which I would propose for addition to Boost. It is a
wrapper around a resource handle, just like std::auto_ptr is a wrapper around
a pointer. The main difference to std::auto_ptr is that it doesn't overload
operator-> and that it can (or, rather, must) be customized. Therefore it can
also be used to hold resources like 'int' (a Unix filedescriptor) or 'FILE*'
and also has the known ownership transfer semantics of std::auto_ptr.
A few points I'd like to point out:
* auto_ptr can hold a FILE*, too!
No it can't, because it invokes delete on it while it wasn't allocated with
new.
* shared_ptr could hold a FILE! Yes, provided you give it a suitable deleter.
However, shared_ptr does not provide exclusive ownership semantics and
requires dynamic allocation for the reference counter.
* It has the same size as std::auto_ptr (I'm assuming an implementation of
auto_ptr that only wraps a pointer) and achieves the custom deleting via a
policy type (type traits).
* When wrapping e.g. an int, the type traits force you to specify whether the
int is a timer ID or a filedescriptor, so you even get something like type
safety on top of a type-depraved API.
* When wrapping e.g. a win32 HANDLE (which is a very opaque type used for lots
of things in the win32 API) you can even create traits that tell whether
something is a windows handle or an event handle, even though both use the
same function for releasing the resource.
* You could wrap pointers, too, but since it isn't intended as a smart pointer
the syntax would be a bit awkward. Of course, the way the pointers are
deallocated would then be customizable, e.g. free() or delete[].
* The same type traits could be used to form a shared_ptr equivalent, but the
places where I used this I haven't found the need for this yet. In fact I
call exclusive ownership an advantage sometimes, because you precisely don't
have to care about whether someone else might be accessing the resource.
Also, there is no release() function which I admit is a dangerous function
but it is useful nonetheless. I'd call it a sharp tool.
I wrote something like this once for a Berkeley socket and once for a win32
thread handle before generalizing it into this class template. It is by no
means production-ready (although the idea itself is) so I only want to
present the library and some example code (see below) for discussion here.
cheers
Uli
/* example program for a handle wrapper that behaves like std::auto_ptr
This wrapper class can be customized with a traits type and then handle
resources that are not released with delete, like C's FILE* or Unix
filedescriptors.
The main characteristics are captured by the trait class. For type FILE, a
suitable specialisation is already provided, for other types you can do the
same. For use with e.g. Unix filedescriptors, I wouldn't specialise the
traits because they are plain ints and thus not a safe identifier for this
kind of use (e.g. under win32, you need to differentiate between close() and
closesocket(), depending on where you got the int from). Instead, you can use
the fact that the first template parameter is only used as default for the
traits and not(!) as real storage type. You then simply introduce a special
type (the mere declaration of a simple, dedicated struct is enough) and the
nested typedef inside the traits is the storage type:
// incomplete dummy type, only used as ID for specialisation below
struct filedescriptor;
// specialize resource_traits
template<>
resource_traits<filedescriptor>
{
typedef int handle_type;
...
};
auto_handle<filedescriptor> h(open("/dev/stdout", O_WRONLY));
if(h)
write( *h, "Heya!", strlen("Heya!"));
Note: other than std::auto_ptr, release() doesn't return the resource handle.
The reason is that this first needs to be copied to a safe place (i.e. one
that guarantees no resource leaks) before releasing ownership.
// wrong: if insert() throws, the handle is lost and the
// resource is leaked!
container.insert(h.release());
// correct:
container.insert(h.get());
h.release();
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
/* traits class describing the resource behaviour */
template<typename ResourceHandle>
struct resource_traits
{
/* nested typedef for the handle type
Typically the same as the template parameter. */
typedef ResourceHandle handle_type;
/* invalid signal value
The equivalent of a NULL pointer. */
handle_type get_null() const;
/* check for invalid signal values
This is useed because there are cases where more than one signal value
exists. */
bool is_valid(handle_type h) const;
/* release the resource
This is the equivalent to delete or free() for the resource type. */
void deallocate(handle_type h) const;
};
template<typename ResourceHandle, typename ResourceTraits =
resource_traits<ResourceHandle> >
struct auto_handle:
ResourceTraits
{
typedef ResourceTraits traits_type;
typedef typename traits_type::handle_type handle_type;
auto_handle(): m_handle(traits_type::get_null()) {}
explicit auto_handle(handle_type h): m_handle(h) {}
auto_handle( auto_handle& rhs): m_handle(rhs.get())
{ rhs.release(); }
auto_handle& operator=(auto_handle& rhs)
{
if(this!=&rhs)
{
reset(rhs.get());
rhs.release();
}
return *this;
}
~auto_handle()
{ traits_type::deallocate(m_handle); }
struct ref
{
handle_type h;
};
operator ref()
{
ref r = {m_handle};
release();
return r;
}
auto_handle(ref r): m_handle(r.h) {}
auto_handle& operator=(ref r)
{
reset(r.h);
return *this;
}
handle_type operator*() const
{ return m_handle; }
handle_type get() const
{ return m_handle; }
bool valid() const
{ return traits_type::is_valid(m_handle); }
typedef bool (auto_handle::*boolean_type)() const;
operator boolean_type() const
{
if(traits_type::is_valid(m_handle))
return &auto_handle::valid;
else
return 0;
}
// reset handle without deallocating associated resource
void release()
{
m_handle = traits_type::get_null();
}
// deallocate resource and reset handle
void reset()
{
traits_type::deallocate(m_handle);
m_handle = traits_type::get_null();
}
void reset( handle_type h)
{
traits_type::deallocate(m_handle);
m_handle = h;
}
private:
handle_type m_handle;
};
template<>
struct resource_traits<FILE*>
{
/* nested typedef for the handle type
Typically the same as the template parameter. */
typedef FILE* handle_type;
/* invalid signal value
The equivalent of a NULL pointer. */
static handle_type get_null()
{ return 0; }
/* compare for invalid signal value
This is useful for cases where more than one signal value exists. */
static bool is_valid(handle_type h)
{ return h!=0; }
/* release the resource
This is the equivalent to delete or free() for the resource type. */
static void deallocate(handle_type h)
{
if(is_valid(h))
{
printf("deallocate(%p)\n", h);
fclose(h);
}
}
};
typedef auto_handle<FILE*> auto_file;
auto_file
open_file(char const* path)
{
auto_handle<FILE*> res(fopen(path,"r"));
printf("open_file('%s') = %p\n", path, res.get());
return res;
}
struct filedescriptor;
template<>
struct resource_traits<filedescriptor>
{
typedef int handle_type;
static handle_type get_null()
{ return -1; }
static bool is_valid(handle_type h)
{ return h>=0; }
static void deallocate(handle_type h)
{
if(is_valid(h))
{
printf("deallocate(%d)\n", h);
// TODO: check returnvalue
close(h);
}
}
};
typedef auto_handle<filedescriptor> auto_filedescriptor;
auto_filedescriptor
open_fd(char const* path)
{
auto_filedescriptor res(open( path, O_RDONLY));
printf("open_fd('%s') = %d\n", path, res.get());
return res;
}
int
main()
{
{
auto_handle<FILE*> f = open_file("/dev/null");
if(!f)
printf("failed to open '/dev/null'\n");
f = open_file("/dev/stdout");
if(!f)
printf("failed to open '/dev/stdout'\n");
open_file("/dev/null");
}
{
auto_filedescriptor fd = open_fd("/dev/null");
if(!fd)
printf("failed to open '/dev/null'\n");
fd = open_fd("/dev/stdout");
if(!fd)
printf("failed to open '/dev/stdout'\n");
open_fd("/dev/null");
}
}
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk