
Hi, (not sure if it was discussed before) I've found Boost smart pointers inflexible in the sense that they operate only on pointers. It would be a good idea to generalize their interface to handle just about anything that requires special care when destroying. HANDLE (on Windows), FILE*, file descriptors (on Unix), third-party API handles, etc. come to mind. I wrote a very simple class (see attachments), scoped_handle, which encapsulates the resource (whatever it is) and its disposing (whatever that means). The techniques used are similar to those used in implementation of boost::shared_ptr. The example file shows how the class can be used to guard resources allocated by operator new, fopen, open, etc. Is it possible to achieve the same results with the existing Boost libraries? If not, is there any interest in developing this kind of class? -- Maciej Sobczak : http://www.msobczak.com/ Programming : http://www.msobczak.com/prog/ // // Copyright (C) 2004 Maciej Sobczak // http://www.msobczak.com/ // // Permission to copy, use, modify, sell and distribute this software // is granted provided this copyright notice appears in all copies. // This software is provided "as is" without express or implied // warranty, and with no claim as to its suitability for any purpose. // #ifndef SCOPED_HANDLER_H_INCLUDED #define SCOPED_HANDLER_H_INCLUDED template <typename Resource> class disposer_base { public: virtual ~disposer_base() {} virtual void dispose(Resource) = 0; }; template <typename Resource, typename Deleter> class disposer_impl : public disposer_base<Resource> { public: disposer_impl(Deleter d) : d_(d) {} void dispose(Resource r) { d_(r); } private: Deleter d_; }; template <typename Resource> void simpleDeleter(Resource r) { delete r; } template <typename Resource> void arrayDeleter(Resource r) { delete [] r; } // requirements: // Resource and Deleter are copy-constructible and do not throw while copying // For Resource r and Deleter d, d(r) is a valid expression that doesn't throw template <typename Resource> class scoped_handle { public: scoped_handle(Resource r) : r_(r) { initDeleter(simpleDeleter<Resource>); } template <typename Deleter> scoped_handle(Resource r, Deleter d) : r_(r) { initDeleter(d); } ~scoped_handle() { disp_->dispose(r_); delete disp_; } Resource get() const { return r_; } private: scoped_handle(const scoped_handle &); scoped_handle & operator=(const scoped_handle &); template <typename Deleter> void initDeleter(Deleter d) { try { disp_ = new disposer_impl<Resource, Deleter>(d); } catch (...) { d(r_); throw; } } disposer_base<Resource> *disp_; Resource r_; }; #endif // SCOPED_HANDLER_H_INCLUDED #include "scoped_handle.h" #include <iostream> #include <functional> // this is for Unix only #include <fcntl.h> #include <unistd.h> using namespace std; class A { public: A() { cout << "A()\n"; } ~A() { cout << "~A()\n"; } }; void specialDeleter(A *p) { cout << "special deleter: "; delete p; } void anotherSpecialDeleter(A *p, int i) { cout << "another special deleter (" << i << "): "; delete p; } int main() { { scoped_handle<A*> h1(new A); scoped_handle<A*> h2(new A, specialDeleter); scoped_handle<A*> h3(new A, bind2nd(ptr_fun(anotherSpecialDeleter), 7)); // resources are released in the reverse order } { scoped_handle<FILE*> h4(fopen("test.txt", "w"), fclose); // ... } { // this is for Unix only scoped_handle<int> h5(open("test.txt", O_RDONLY), close); // ... } // etc. }

Maciej Sobczak wrote: []
I wrote a very simple class (see attachments), scoped_handle, which encapsulates the resource (whatever it is) and its disposing (whatever that means). The techniques used are similar to those used in implementation of boost::shared_ptr. The example file shows how the class can be used to guard resources allocated by operator new, fopen, open, etc.
Well, the example has one major drawback - it uses new. The solution with shared_ptr has the same drawback. It looks overkill to use it for such a simple problem. I believe that scoped_handle must be as lightweight as possible. There is also a belief that mythical policy based smart pointer will solve the problem, but I needed it yesterday. I've been using following simple implementation. Not a rocket science but it suited my needs quite well. #include <boost/noncopyable.hpp> template<class Handle> struct scoped_handle_traits { static Handle null() { return Handle(); } }; template< class Handle , class Pfn , Pfn close , class Tag , class Traits = scoped_handle_traits<Handle> > class scoped_handle : private boost::noncopyable { public: typedef Tag tag_type; typedef Handle handle_type; typedef Traits traits_type; public: scoped_handle() : h_(traits_type::null()) {} explicit scoped_handle(Handle h) : h_(h) {} ~scoped_handle() { if (traits_type::null() != h_) close(h_); } Handle& get_ref() { return h_; } Handle get() const { return h_; } void reset(Handle h) { scoped_handle(h).swap(*this); } Handle release() { Handle t(h_); h_ = traits_type::null(); return t; } void swap(scoped_handle& other) { std::swap(h_, other.h_); } private: typedef Handle(scoped_handle::*unspecified_bool_type)(); public: operator unspecified_bool_type() const { return traits_type::null() == h_ ? false : &scoped_handle::release; } private: Handle h_; }; Example of usage: typedef scoped_handle<HANDLE, BOOL(WINAPI*)(HANDLE), &::CloseHandle, class handle_tag> handle; typedef scoped_handle<HLOCAL, HLOCAL(WINAPI*)(HLOCAL), &::LocalFree, class hlocal_tag> hlocal; -- Maxim Yegorushkin

Hi, Maxim Yegorushkin wrote:
I wrote a very simple class (see attachments), scoped_handle, which encapsulates the resource (whatever it is) and its disposing (whatever that means). The techniques used are similar to those used in implementation of boost::shared_ptr. The example file shows how the class can be used to guard resources allocated by operator new, fopen, open, etc.
Well, the example has one major drawback - it uses new. The solution with shared_ptr has the same drawback. It looks overkill to use it for such a simple problem.
It depends on how you count this "simplicity". For 99% cases I can think of (in fact, for 100% - the 1% margin is for my humble assumption that one cannot think of everything ;) ), the smart handle would be needed to provide exception safety to raw handles which manage some external resources (files, sockets, DB connections, sessions, statements, transactions, processes, etc.) and which automate their cleanup. All these resources take much more time (usually orderS of magnitude) to allocate and cleanup than a single new. I've yet to see a realistic example where such resource is significantly cheaper than operator new. And even if this is realistic, it would be easy to extend the machinery to use some tuned allocation strategy, like recycling memory pool, so that the allocation by new comes down to just few CPU cycles. In other words, I do not consider this solution to be an overkill. Moreover, it is quite cheap and can buy some interesting features - for example, you can change the deleter at run-time (the basic Strategy pattern).
I believe that scoped_handle must be as lightweight as possible.
Yes. But no lighter. ;)
I've been using following simple implementation. Not a rocket science but it suited my needs quite well.
template<class Handle> struct scoped_handle_traits { static Handle null() { return Handle(); } }; [...]
Interesting, but I would add is_null() predicate to the traits. This is so that the Handle does not need to define its own operator== and operator!= (but you can always delegate the generic traits to these operators).
template< class Handle , class Pfn , Pfn close , class Tag , class Traits = scoped_handle_traits<Handle> > class scoped_handle : private boost::noncopyable [...]
{ public: typedef Tag tag_type; [...]
Would you please explain what is the need for Tag or tag_type?
Example of usage:
typedef scoped_handle<HANDLE, BOOL(WINAPI*)(HANDLE), &::CloseHandle, class handle_tag> handle; typedef scoped_handle<HLOCAL, HLOCAL(WINAPI*)(HLOCAL), &::LocalFree, class hlocal_tag> hlocal;
-- Maciej Sobczak : http://www.msobczak.com/ Programming : http://www.msobczak.com/prog/

Maciej Sobczak <prog@msobczak.com> wrote:
It depends on how you count this "simplicity". For 99% cases I can think of (in fact, for 100% - the 1% margin is for my humble assumption that one cannot think of everything ;) ), the smart handle would be needed to provide exception safety to raw handles which manage some external resources (files, sockets, DB connections, sessions, statements, transactions, processes, etc.) and which automate their cleanup. All these resources take much more time (usually orderS of magnitude) to allocate and cleanup than a single new.
I've yet to see a realistic example where such resource is significantly cheaper than operator new. And even if this is realistic, it would be easy to extend the machinery to use some tuned allocation strategy, like recycling memory pool, so that the allocation by new comes down to just few CPU cycles.
In other words, I do not consider this solution to be an overkill. Moreover, it is quite cheap and can buy some interesting features - for example, you can change the deleter at run-time (the basic Strategy pattern).
That does make sence. []
Interesting, but I would add is_null() predicate to the traits. This is so that the Handle does not need to define its own operator== and operator!= (but you can always delegate the generic traits to these operators).
Agreed.
template< class Handle , class Pfn , Pfn close , class Tag , class Traits = scoped_handle_traits<Handle> > class scoped_handle : private boost::noncopyable [...]
{ public: typedef Tag tag_type; [...]
Would you please explain what is the need for Tag or tag_type?
The example is win32 handles. Being different entities they may have the same HANDLE type and are closed using CloseHandle(). I needed to make overloads on scoped_handle<> type, so as scoped_handle<> typedef for such handles would be the same type I had to add Tag template parameter. -- Maxim Yegorushkin
participants (2)
-
Maciej Sobczak
-
Maxim Yegorushkin