How to handle failure to release a resource which is contained in a smart pointer?
Asked Answered
T

6

9

How should an error during resource deallocation be handled, when the object representing the resource is contained in a shared pointer?

EDIT 1:

To put this question in more concrete terms: Many C-style interfaces have a function to allocate a resource, and one to release it. Examples are open(2) and close(2) for file descriptors on POSIX systems, XOpenDisplay and XCloseDisplay for a connection to an X server, or sqlite3_open and sqlite3_close for a connection to an SQLite database.

I like to encapsulate such interfaces in a C++ class, using the Pimpl idiom to hide the implementation details, and providing a factory method returning a shared pointer to ensure that the resource is deallocated when no references to it remain.

But, in all the examples given above and many others, the function used to release the resource may report an error. If this function is called by the destructor, I cannot throw an exception because generally destructors must not throw.

If, on the other hand, I provide a public method to release the resource, I now have a class with two possible states: One in which the resource is valid, and one in which the resource has already been released. Not only does this complicate the implementation of the class, it also opens a potential for wrong usage. This is bad, because an interface should aim to make usage errors impossible.

I would be grateful for any help with this problem.

The original statement of the question, and thoughts about a possible solution follow below.

EDIT 2:

There is now a bounty on this question. A solution must meet these requirements:

  1. The resource is released if and only if no references to it remain.
  2. References to the resource may be destroyed explicitly. An exception is thrown if an error occured while releasing the resource.
  3. It is not possible to use a resource which has already been released.
  4. Reference counting and releasing of the resource are thread-safe.

A solution should meet these requirements:

  1. It uses the shared pointer provided by boost, the C++ Technical Report 1 (TR1), and the upcoming C++ standard, C++0x.
  2. It is generic. Resource classes only need to implement how the resource is released.

Thank you for your time and thoughts.

EDIT 3:

Thanks to everybody who answered my question.

Alsk's answer met everything asked for in the bounty, and was accepted. In multithreaded code, this solution would require a separate cleanup thread.

I have added another answer where any exceptions during cleanup are thrown by the thread that actually used the resource, without need for a separate cleanup thread. If you are still interested in this problem (it bothered me a lot), please comment.

Smart pointers are a useful tool to manage resources safely. Examples of such resources are memory, disk files, database connections, or network connections.

// open a connection to the local HTTP port
boost::shared_ptr<Socket> socket = Socket::connect("localhost:80");

In a typical scenario, the class encapsulating the resource should be noncopyable and polymorphic. A good way to support this is to provide a factory method returning a shared pointer, and declare all constructors non-public. The shared pointers can now be copied from and assigned to freely. The object is automatically destroyed when no reference to it remains, and the destructor then releases the resource.

/** A TCP/IP connection. */
class Socket
{
public:
    static boost::shared_ptr<Socket> connect(const std::string& address);
    virtual ~Socket();
protected:
    Socket(const std::string& address);
private:
    // not implemented
    Socket(const Socket&);
    Socket& operator=(const Socket&);
};

But there is a problem with this approach. The destructor must not throw, so a failure to release the resource will remain undetected.

A common way out of this problem is to add a public method to release the resource.

class Socket
{
public:
    virtual void close(); // may throw
    // ...
};

Unfortunately, this approach introduces another problem: Our objects may now contain resources which have already been released. This complicates the implementation of the resource class. Even worse, it makes it possible for clients of the class to use it incorrectly. The following example may seem far-fetched, but it is a common pitfall in multi-threaded code.

socket->close();
// ...
size_t nread = socket->read(&buffer[0], buffer.size()); // wrong use!

Either we ensure that the resource is not released before the object is destroyed, thereby losing any way to deal with a failed resource deallocation. Or we provide a way to release the resource explicitly during the object's lifetime, thereby making it possible to use the resource class incorrectly.

There is a way out of this dilemma. But the solution involves using a modified shared pointer class. These modifications are likely to be controversial.

Typical shared pointer implementations, such as boost::shared_ptr, require that no exception be thrown when their object's destructor is called. Generally, no destructor should ever throw, so this is a reasonable requirement. These implementations also allow a custom deleter function to be specified, which is called in lieu of the destructor when no reference to the object remains. The no-throw requirement is extended to this custom deleter function.

The rationale for this requirement is clear: The shared pointer's destructor must not throw. If the deleter function does not throw, nor will the shared pointer's destructor. However, the same holds for other member functions of the shared pointer which lead to resource deallocation, e.g. reset(): If resource deallocation fails, no exception can be thrown.

The solution proposed here is to allow custom deleter functions to throw. This means that the modified shared pointer's destructor must catch exceptions thrown by the deleter function. On the other hand, member functions other than the destructor, e.g. reset(), shall not catch exceptions of the deleter function (and their implementation becomes somewhat more complicated).

Here is the original example, using a throwing deleter function:

/** A TCP/IP connection. */
class Socket
{
public:
    static SharedPtr<Socket> connect(const std::string& address);
protected:
    Socket(const std::string& address);
    virtual Socket() { }
private:
    struct Deleter;

    // not implemented
    Socket(const Socket&);
    Socket& operator=(const Socket&);
};

struct Socket::Deleter
{
    void operator()(Socket* socket)
    {
        // Close the connection. If an error occurs, delete the socket
        // and throw an exception.

        delete socket;
    }
};

SharedPtr<Socket> Socket::connect(const std::string& address)
{
    return SharedPtr<Socket>(new Socket(address), Deleter());
}

We can now use reset() to free the resource explicitly. If there is still a reference to the resource in another thread or another part of the program, calling reset() will only decrement the reference count. If this is the last reference to the resource, the resource is released. If resource deallocation fails, an exception is thrown.

SharedPtr<Socket> socket = Socket::connect("localhost:80");
// ...
socket.reset();

EDIT:

Here is a complete (but platform-dependent) implementation of the deleter:

struct Socket::Deleter
{
    void operator()(Socket* socket)
    {
        if (close(socket->m_impl.fd) < 0)
        {
            int error = errno;
            delete socket;
            throw Exception::fromErrno(error);
        }

        delete socket;
     }
};
Treviso answered 16/5, 2010 at 19:39 Comment(3)
See also Herb Sutter's GotW #47: Uncaught Exceptions (the OP seems familiar with the concepts discussed there; I'm just posting it for any other interested parties).Sylviasylviculture
Not a real question: Also this subject is covered much better in "Scott Myers has an excellent article about the subject in his book "Effective C++""Prader
This is a question, and one I've been meant to ask here for a long time. I just edited my question to clarify that I am actually seeking potential solutions to the problem, as well as a critique of my own solution, which I regard as imperfect.Treviso
O
4

We need to store allocated resources somewhere (as it was already mentioned by DeadMG) and explicitly call some reporting/throwing function outside of any destructor. But that doesn't prevent us from taking advantage of reference counting implemented in boost::shared_ptr.

/** A TCP/IP connection. */
class Socket
{
private:
    //store internally every allocated resource here
    static std::vector<boost::shared_ptr<Socket> > pool;
public:
    static boost::shared_ptr<Socket> connect(const std::string& address)
    {
         //...
         boost::shared_ptr<Socket> socket(new Socket(address));
         pool.push_back(socket); //the socket won't be actually 
                                 //destroyed until we want it to
         return socket;
    }
    virtual ~Socket();

    //call cleanupAndReport() as often as needed
    //probably, on a separate thread, or by timer 
    static void cleanupAndReport()
    {
        //find resources without clients
        foreach(boost::shared_ptr<Socket>& socket, pool)
        {
            if(socket.unique()) //there are no clients for this socket, i.e. 
                  //there are no shared_ptr's elsewhere pointing to this socket
            {
                 //try to deallocate this resource
                 if (close(socket->m_impl.fd) < 0)
                 {
                     int error = errno;
                     socket.reset(); //destroys Socket object
                     //throw an exception or handle error in-place
                     //... 
                     //throw Exception::fromErrno(error);
                 }
                 else
                 {
                     socket.reset();
                 } 
            } 
        } //foreach socket
    }
protected:
    Socket(const std::string& address);
private:
    // not implemented
    Socket(const Socket&);
    Socket& operator=(const Socket&);
};

The implementation of cleanupAndReport() should be a little more complicated: in the present version the pool is populated with null pointers after cleanup, and in case of throwing exception we have to call the function until it doesn't throw anymore etc, but I hope, it illustrates well the idea.

Now, more general solution:

//forward declarations
template<class Resource>
boost::shared_ptr<Resource> make_shared_resource();
template<class Resource>
void cleanupAndReport(boost::function1<void,boost::shared_ptr<Resource> deallocator);

//for every type of used resource there will be a template instance with a static pool
template<class Resource>
class pool_holder
{
private:
        friend boost::shared_ptr<Resource> make_shared_resource<Resource>();
        friend void cleanupAndReport(boost::function1<void,boost::shared_ptr<Resource>);
        static std::vector<boost::shared_ptr<Resource> > pool;
};
template<class Resource>
std::vector<boost::shared_ptr<Resource> > pool_holder<Resource>::pool;

template<class Resource>
boost::shared_ptr<Resource> make_shared_resource()
{
        boost::shared_ptr<Resource> res(new Resource);
        pool_holder<Resource>::pool.push_back(res);
        return res;
}
template<class Resource>
void cleanupAndReport(boost::function1<void,boost::shared_ptr<Resource> > deallocator)
{
    foreach(boost::shared_ptr<Resource>& res, pool_holder<Resource>::pool)
    {
        if(res.unique()) 
        {
             deallocator(res);
        }
    } //foreach
}
//usage
        {
           boost::shared_ptr<A> a = make_shared_resource<A>();
           boost::shared_ptr<A> a2 = make_shared_resource<A>();
           boost::shared_ptr<B> b = make_shared_resource<B>();
           //...
        }
        cleanupAndReport<A>(deallocate_A);
        cleanupAndReport<B>(deallocate_B);
Ormandy answered 24/5, 2010 at 21:29 Comment(3)
Thank you! IMO this meets all ("must" and "should") requirements. Keeping a pool and using a separate cleanup function is a great idea, because it makes the code thread-safe. I experimented with a free-standing destroy(ptr) function which checks the use_count before releasing the resource ("if unique then deallocate, finally reset ptr"); the destructor would also release the resource if need be, but swallow exceptions. But this had a race condition: two threads might both think they weren't the last ones, thus losing the potential exception. Your solution doesn't have this problem.Treviso
I should accept this answer, as it meets all requirements stated. But I am still hoping for a solution where (in case of error) the exception is generated by the thread using the resource, rather than a separate cleanup thread. (Cannot upvote, spent all reputation on the bounty, silly me...). If such a thing is not possible, I will definitely accept.Treviso
Glad to help) Also, to make make_shared_resource() function more convenient in use (accept arguments for resource's constructor etc), you could consider the implementation of make_shared() in Boost.Smart_ptrOrmandy
P
4

If releasing some resource can actually fail, then a destructor is clearly a wrong abstraction to use. Destructors are meant to clean up without fail, regardless of the circumstances. A close() method (or whatever you want to name it) is probably the only way to go.

But think closely about it. If releasing a resource actually fails, what can you do? Is such an error recoverable? If it is, which part of your code should handle it? The way to recover is probably highly application-specific and tied to other parts of the application. It is highly unlikely that you actually want that to happen automatically, in an arbitrary place in the code that happened to release the resource and trigger the error. A shared pointer abstraction does not really model what you're trying to achieve. If so, then you clearly need to create your own abstraction which models your requested behavior. Abusing shared pointers to do something they're not supposed to do is not the right way.

Also, please read this.

EDIT:
If all you want to do is to inform the user what happened before crashing, then consider wrapping the Socket in another wrapper object that would call the deleter on its destruction, catch any exceptions thrown and handle them by showing the user a message box or whatever. Then put this wrapper object inside a boost::shared_ptr.

Pennipennie answered 16/5, 2010 at 20:59 Comment(10)
If releasing a resource fails, and the error is not recoverable, I need to at least report the problem to the user. All that is required for this is to throw an exception when the resource is released, and deal with it somewhere else in the code. This is no misuse of shared pointers.Treviso
I am not using a destructor anywhere to indicate failure by throwing an exception. I am using a custom deleter function, which is not a destructor, and only when it is not called in the context of the shared pointer's destructor.Treviso
That is where you misuse the shared pointer. When you call release or ~ on a shared pointer the concept means that the resource behind it has been deleted if that caused the count to fall to 0. What you want goes beyond the boundaries of this concept. You simply need something smarter than shared pointers where ever designed to be.Denny
This is misuse of shared pointers. The whole idea of shared pointers rests on the assumption that you can destroy the managed object without fail. Otherwise, what would happen when you destroy the object during unwinding because of some other exception?Pennipennie
Your solution tries to sidestep the problem by having the owner of the "extended shared pointer" kill it explicitly in normal circumstances (i.e. no other exception being handled), so that it can pass through an exception from the deleter. The problem is, that this exception is lost when the last "smart-ass pointer" gets killed through the destructor, not the reset() method.Pennipennie
@Noah If reset() or the destructor is called on the modified shared pointer, and the resource count falls to zero, the object is deleted and the resource is released. The only difference in my implementation is that an exception is thrown at the end of reset() if there was an error. E.g. if some remaining data could not be written over an I/O channel before it was closed.Treviso
@Pennipennie "what would happen when you destroy the object during unwinding because of some other exception" - In this case, the shared pointer's destructor is called, not its reset() function. The shared pointer's destructor suppresses any exception thrown by the deleter function.Treviso
@Pennipennie "[Your solution has] the owner of the "extended shared pointer" kill it explicitly in normal circumstances [...] so that it can pass through an exception from the deleter. [...] this exception is lost when the last "smart-ass pointer" gets killed through the destructor [...]" - Exactly! This provides a way to explicitly release the resource with exceptions, using reset(). But if the object is destroyed before that, during stack unwinding due to another exception, any exception from releasing the resource is suppressed, as it should be.Treviso
@Pennipennie Your edit proposes to use a wrapper object's destructor to release the resource and handle potential errors. Either this requires that there be a public close() method, with the problems outlined in my original post. Or the wrapper has access to the resource class's implementation (e.g. friend or nested class). This breaks encapsulation in a bad way because a class with knowledge of how to handle errors (reporting errors using a logger or a GUI, or even attempting to recover from errors) would be effectively part of the low-level resource class's interface.Treviso
Yes, it assumes a public close(). But avoids reimplementing the boost::shared_ptr. And all the associated pitfalls boost guys already solved.Pennipennie
O
4

We need to store allocated resources somewhere (as it was already mentioned by DeadMG) and explicitly call some reporting/throwing function outside of any destructor. But that doesn't prevent us from taking advantage of reference counting implemented in boost::shared_ptr.

/** A TCP/IP connection. */
class Socket
{
private:
    //store internally every allocated resource here
    static std::vector<boost::shared_ptr<Socket> > pool;
public:
    static boost::shared_ptr<Socket> connect(const std::string& address)
    {
         //...
         boost::shared_ptr<Socket> socket(new Socket(address));
         pool.push_back(socket); //the socket won't be actually 
                                 //destroyed until we want it to
         return socket;
    }
    virtual ~Socket();

    //call cleanupAndReport() as often as needed
    //probably, on a separate thread, or by timer 
    static void cleanupAndReport()
    {
        //find resources without clients
        foreach(boost::shared_ptr<Socket>& socket, pool)
        {
            if(socket.unique()) //there are no clients for this socket, i.e. 
                  //there are no shared_ptr's elsewhere pointing to this socket
            {
                 //try to deallocate this resource
                 if (close(socket->m_impl.fd) < 0)
                 {
                     int error = errno;
                     socket.reset(); //destroys Socket object
                     //throw an exception or handle error in-place
                     //... 
                     //throw Exception::fromErrno(error);
                 }
                 else
                 {
                     socket.reset();
                 } 
            } 
        } //foreach socket
    }
protected:
    Socket(const std::string& address);
private:
    // not implemented
    Socket(const Socket&);
    Socket& operator=(const Socket&);
};

The implementation of cleanupAndReport() should be a little more complicated: in the present version the pool is populated with null pointers after cleanup, and in case of throwing exception we have to call the function until it doesn't throw anymore etc, but I hope, it illustrates well the idea.

Now, more general solution:

//forward declarations
template<class Resource>
boost::shared_ptr<Resource> make_shared_resource();
template<class Resource>
void cleanupAndReport(boost::function1<void,boost::shared_ptr<Resource> deallocator);

//for every type of used resource there will be a template instance with a static pool
template<class Resource>
class pool_holder
{
private:
        friend boost::shared_ptr<Resource> make_shared_resource<Resource>();
        friend void cleanupAndReport(boost::function1<void,boost::shared_ptr<Resource>);
        static std::vector<boost::shared_ptr<Resource> > pool;
};
template<class Resource>
std::vector<boost::shared_ptr<Resource> > pool_holder<Resource>::pool;

template<class Resource>
boost::shared_ptr<Resource> make_shared_resource()
{
        boost::shared_ptr<Resource> res(new Resource);
        pool_holder<Resource>::pool.push_back(res);
        return res;
}
template<class Resource>
void cleanupAndReport(boost::function1<void,boost::shared_ptr<Resource> > deallocator)
{
    foreach(boost::shared_ptr<Resource>& res, pool_holder<Resource>::pool)
    {
        if(res.unique()) 
        {
             deallocator(res);
        }
    } //foreach
}
//usage
        {
           boost::shared_ptr<A> a = make_shared_resource<A>();
           boost::shared_ptr<A> a2 = make_shared_resource<A>();
           boost::shared_ptr<B> b = make_shared_resource<B>();
           //...
        }
        cleanupAndReport<A>(deallocate_A);
        cleanupAndReport<B>(deallocate_B);
Ormandy answered 24/5, 2010 at 21:29 Comment(3)
Thank you! IMO this meets all ("must" and "should") requirements. Keeping a pool and using a separate cleanup function is a great idea, because it makes the code thread-safe. I experimented with a free-standing destroy(ptr) function which checks the use_count before releasing the resource ("if unique then deallocate, finally reset ptr"); the destructor would also release the resource if need be, but swallow exceptions. But this had a race condition: two threads might both think they weren't the last ones, thus losing the potential exception. Your solution doesn't have this problem.Treviso
I should accept this answer, as it meets all requirements stated. But I am still hoping for a solution where (in case of error) the exception is generated by the thread using the resource, rather than a separate cleanup thread. (Cannot upvote, spent all reputation on the bounty, silly me...). If such a thing is not possible, I will definitely accept.Treviso
Glad to help) Also, to make make_shared_resource() function more convenient in use (accept arguments for resource's constructor etc), you could consider the implementation of make_shared() in Boost.Smart_ptrOrmandy
B
1

Quoting Herb Sutter, author of "Exceptional C++" (from here):

If a destructor throws an exception, Bad Things can happen. Specifically, consider code like the following:

//  The problem
//
class X {
public:
  ~X() { throw 1; }
};

void f() {
  X x;
  throw 2;
} // calls X::~X (which throws), then calls terminate()

If a destructor throws an exception while another exception is already active (i.e., during stack unwinding), the program is terminated. This is usually not a good thing.

In other words, regardless of what you would want to believe is elegant in this situation, you cannot blithely throw an exception in a destructor unless you can guarantee that it will not be thrown while handling another exception.

Besides, what can you do if you can't successfully get rid of a resource? Exceptions should be thrown for things that can be handled higher up, not bugs. If you want to report odd behavior, log the release failure and simply go on. Or terminate.

Beverleebeverley answered 24/5, 2010 at 16:57 Comment(2)
I was not proposing to throw an exception in a destructor. Also, if an error occurs while releasing the resource (e.g. NFS cannot write the remaining data to the remote disk) this is not a "bug" but an error condition which must be signaled by an exception if possible.Treviso
Because release can have serious side effects, you should not use RAII to handle those resources. You should use a mechanism that allows you to handle failures on release. You will probably have to make one up on your own.Beverleebeverley
T
1

As announced in the question, edit 3:

Here is another solution which, as far as I can judge, fulfills the requirements in the question. It is similar to the solution described in the original question, but uses boost::shared_ptr instead of a custom smart pointer.

The central idea of this solution is to provide a release() operation on shared_ptr. If we can make the shared_ptr give up its ownership, we are free to call a cleanup function, delete the object, and throw an exception in case an error occurred during cleanup.

Boost has a good reason to not provide a release() operation on shared_ptr:

shared_ptr cannot give away ownership unless it's unique() because the other copy will still destroy the object.

Consider:

shared_ptr<int> a(new int);
shared_ptr<int> b(a); // a.use_count() == b.use_count() == 2

int * p = a.release();

// Who owns p now? b will still call delete on it in its destructor.

Furthermore, the pointer returned by release() would be difficult to deallocate reliably, as the source shared_ptr could have been created with a custom deleter.

The first argument against a release() operation is that, by the nature of shared_ptr, many pointers share ownership of the object, so no single one of them can simply release that ownership. But what if the release() function returned a null pointer if there were still other references left? The shared_ptr can reliably determine this, without race conditions.

The second argument against the release() operation is that, if a custom deleter was passed to the shared_ptr, you should use that to deallocate the object, rather than simply deleting it. But release() could return a function object, in addition to the raw pointer, to enable its caller to deallocate the pointer reliably.

However, in our specific szenario, custom deleters will not be an issue, because we do not have to deal with arbitrary custom deleters. This will become clearer from the code given below.

Providing a release() operation on shared_ptr without modifying its implementation is, of course, not possible without a hack. The hack which is used in the code below relies on a thread-local variable to prevent our custom deleter from actually deleting the object.

That said, here's the code, consisting mostly of the header Resource.hpp, plus a small implementation file Resource.cpp. Note that it must be linked with -lboost_thread-mt due to the thread-local variable.

// ---------------------------------------------------------------------
// Resource.hpp
// ---------------------------------------------------------------------

#include <boost/assert.hpp>
#include <boost/ref.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread/tss.hpp>


/// Factory for a resource.
template<typename T>
struct ResourceFactory
{
    /// Create a resource.
    static boost::shared_ptr<T>
    create()
    {
        return boost::shared_ptr<T>(new T, ResourceFactory());
    }

    template<typename A1>
    static boost::shared_ptr<T>
    create(const A1& a1)
    {
        return boost::shared_ptr<T>(new T(a1), ResourceFactory());
    }

    template<typename A1, typename A2>
    static boost::shared_ptr<T>
    create(const A1& a1, const A2& a2)
    {
        return boost::shared_ptr<T>(new T(a1, a2), ResourceFactory());
    }

    // ...

    /// Destroy a resource.
    static void destroy(boost::shared_ptr<T>& resource);

    /// Deleter for boost::shared_ptr<T>.
    void operator()(T* resource);
};


namespace impl
{

// ---------------------------------------------------------------------

/// Return the last reference to the resource, or zero. Resets the pointer.
template<typename T>
T* release(boost::shared_ptr<T>& resource);

/// Return true if the resource should be deleted (thread-local).
bool wantDelete();

// ---------------------------------------------------------------------

} // namespace impl


template<typename T>
inline
void ResourceFactory<T>::destroy(boost::shared_ptr<T>& ptr)
{
    T* resource = impl::release(ptr);

    if (resource != 0) // Is it the last reference?
    {
        try
        {
            resource->close();
        }
        catch (...)
        {
            delete resource;

            throw;
        }

        delete resource;
    }
}

// ---------------------------------------------------------------------

template<typename T>
inline
void ResourceFactory<T>::operator()(T* resource)
{
    if (impl::wantDelete())
    {
        try
        {
            resource->close();
        }
        catch (...)
        {
        }

        delete resource;
    }
}


namespace impl
{

// ---------------------------------------------------------------------

/// Flag in thread-local storage.
class Flag
{
public:
    ~Flag()
    {
        m_ptr.release();
    }

    Flag& operator=(bool value)
    {
        if (value != static_cast<bool>(*this))
        {
            if (value)
            {
                m_ptr.reset(s_true); // may throw boost::thread_resource_error!
            }
            else
            {
                m_ptr.release();
            }
        }

        return *this;
    }

    operator bool()
    {
        return m_ptr.get() == s_true;
    }

private:
    boost::thread_specific_ptr<char> m_ptr;

    static char* s_true;
};

// ---------------------------------------------------------------------

/// Flag to prevent deletion.
extern Flag t_nodelete;

// ---------------------------------------------------------------------

/// Return the last reference to the resource, or zero.
template<typename T>
T* release(boost::shared_ptr<T>& resource)
{
    try
    {
        BOOST_ASSERT(!t_nodelete);

        t_nodelete = true; // may throw boost::thread_resource_error!
    }
    catch (...)
    {
        t_nodelete = false;

        resource.reset();

        throw;
    }

    T* rv = resource.get();

    resource.reset();

    return wantDelete() ? rv : 0;
}

// ---------------------------------------------------------------------

} // namespace impl

And the implementation file:

// ---------------------------------------------------------------------
// Resource.cpp
// ---------------------------------------------------------------------

#include "Resource.hpp"


namespace impl
{

// ---------------------------------------------------------------------

bool wantDelete()
{
    bool rv = !t_nodelete;

    t_nodelete = false;

    return rv;
}

// ---------------------------------------------------------------------

Flag t_nodelete;

// ---------------------------------------------------------------------

char* Flag::s_true((char*)0x1);

// ---------------------------------------------------------------------

} // namespace impl

And here is an example of a resource class implemented using this solution:

// ---------------------------------------------------------------------
// example.cpp
// ---------------------------------------------------------------------
#include "Resource.hpp"

#include <cstdlib>
#include <string>
#include <stdexcept>
#include <iostream>


// uncomment to test failed resource allocation, usage, and deallocation

//#define TEST_CREAT_FAILURE
//#define TEST_USAGE_FAILURE
//#define TEST_CLOSE_FAILURE

// ---------------------------------------------------------------------

/// The low-level resource type.
struct foo { char c; };

// ---------------------------------------------------------------------

/// The low-level function to allocate the resource.
foo* foo_open()
{
#ifdef TEST_CREAT_FAILURE
    return 0;
#else
    return (foo*) std::malloc(sizeof(foo));
#endif
}

// ---------------------------------------------------------------------

/// Some low-level function using the resource.
int foo_use(foo*)
{
#ifdef TEST_USAGE_FAILURE
    return -1;
#else
    return 0;
#endif
}

// ---------------------------------------------------------------------

/// The low-level function to free the resource.
int foo_close(foo* foo)
{
    std::free(foo);
#ifdef TEST_CLOSE_FAILURE
    return -1;
#else
    return 0;
#endif
}

// ---------------------------------------------------------------------

/// The C++ wrapper around the low-level resource.
class Foo
{
public:
    void use()
    {
        if (foo_use(m_foo) < 0)
        {
            throw std::runtime_error("foo_use");
        }
    }

protected:
    Foo()
        : m_foo(foo_open())
    {
        if (m_foo == 0)
        {
            throw std::runtime_error("foo_open");
        }
    }

    void close()
    {
        if (foo_close(m_foo) < 0)
        {
            throw std::runtime_error("foo_close");
        }
    }

private:
    foo* m_foo;

    friend struct ResourceFactory<Foo>;
};

// ---------------------------------------------------------------------

typedef ResourceFactory<Foo> FooFactory;

// ---------------------------------------------------------------------

/// Main function.
int main()
{
    try
    {
        boost::shared_ptr<Foo> resource = FooFactory::create();

        resource->use();

        FooFactory::destroy(resource);
    }
    catch (const std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

Finally, here is a small Makefile to build all that:

# Makefile

CXXFLAGS = -g -Wall

example: example.cpp Resource.hpp Resource.o
    $(CXX) $(CXXFLAGS) -o example example.cpp Resource.o -lboost_thread-mt

Resource.o: Resource.cpp Resource.hpp
    $(CXX) $(CXXFLAGS) -c Resource.cpp -o Resource.o

clean:
    rm -f Resource.o example
Treviso answered 16/6, 2010 at 23:26 Comment(0)
D
0

Well, first off, I don't see a question here. Second off, I have to say that this is a bad idea. What will you gain in all this? When the last shared pointer to a resource is destroyed and your throwing deleter is called you will find yourself with a resource leak. You will have lost all handles to the resource that failed to release. You will never be able to try again.

Your desire to use an RAII object is a good one but a smart pointer is simply insufficient to the task. What you need needs to be even smarter. You need something that can rebuild itself on failure to completely collapse. The destructor is insufficient for such an interface.

You do introduce yourself to the misuse where someone could cause a resource to have a handle but be invalid. The type of resource you're dealing with here simply lends itself to this issue. There are many ways in which you may approach this. One method may be to use the handle/body idiom along with the state pattern. The implementation of the interface can be in one of two states: connected or unconnected. The handle simply passes requests to the internal body/state. Connected works like normal, unconnected throws exceptions/asserts in all applicable requests.

This thing would need a function other than ~ to destroy a handle to it. You could consider a destroy() function that can throw. If you catch an error when you call it you don't delete the handle but instead deal with the problem in whatever application specific way you need to. If you don't catch an error from destroy() you let the handle go out of scope, reset it, or whatever. The function destroy() then decriments the resource count and attempts to release the internal resource if that count is 0. Upon success the handle in switched to the unconnected state, upon failure it generates a catchable error that the client can attempt to handle but leaves the handle in a connected state.

It's not an entirely trivial thing to write but what you are wanting to do, introduce exceptions into destruction, simply will not work.

Denny answered 16/5, 2010 at 20:4 Comment(0)
A
0

Generally speaking, if a resource's C-style closure fails, then it's a problem with the API rather than a problem in your code. However, what I would be tempted to do is, if destruction is failed, add it to a list of resources that need destruction/cleanup re-attempted later, say, when app exits, periodically, or when other similar resources are destroyed, and then try to re-destroy. If any are left over at arbitrary time, give user error and exit.

Achaean answered 21/5, 2010 at 20:3 Comment(1)
But the cleanup functions I gave as examples always destroy the resource. So the problem I'm dealing with isn't to destroy the resource, but to signal that the resource was left in a bad state. Here is an example from my manpages: "Not checking the return value of close() is a common but nevertheless serious programming error. It is quite possible that errors on a previous write(2) operation are first reported at the final close(). Not checking the return value when closing the file may lead to silent loss of data. This can especially be observed with NFS and with disk quota."Treviso

© 2022 - 2024 — McMap. All rights reserved.