helper for managing legacy resources

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"); } }
participants (1)
-
Ulrich Eckhardt