|
Boost : |
From: Mike Tegtmeyer (tegtmeye_at_[hidden])
Date: 2007-11-09 09:05:22
I'm interested too.
It seems that it is possible to shoe-horn some of this functionality
with shared_ptr but that fact that it doesn't work out of the box or
doesn't work at all with things that both of us have mentioned
(anything not a pointer).
I also think that it is worth looking at it for two additional reasons:
- I suspect most have a custom utility that does this type of
behavior in their own toolbox. (even std::{i,o}stream has sentry)
- In the case where you just need 'scope-based lifetime' as you put
it or exception safety, it is usually not worth the overhead of
shared_ptr
Mike
On Nov 9, 2007, at 1:06 AM, Ulrich Eckhardt wrote:
> [Note: this is a repost, because there seems to be interest but at
> the time
> of the first post nobody responded.]
>
> 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");
> }
> }
>
> _______________________________________________
> Unsubscribe & other changes: http://lists.boost.org/mailman/
> listinfo.cgi/boost
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk