如何处理无法释放智能指针中包含的资源?

时间:2010-05-16 19:39:30

标签: c++ smart-pointers raii

6 个答案:

答案 0 :(得分:4)

如果释放某些资源实际上失败,那么析构函数显然是一个错误的抽象使用。无论情况如何,析构函数都是为了清理而不失败。 close()方法(或任何你想要命名的方法)可能是唯一的方法。

但仔细思考一下。如果实际发布资源失败,您可以做什么?这样的错误是否可以恢复?如果是,代码的哪一部分应该处理它?恢复的方式可能是高度特定于应用程序的,并且与应用程序的其他部分相关联。您实际上希望在发生资源并触发错误的代码中的任意位置实际发生自动的可能性极小。共享指针抽象并不能真正模拟您要实现的目标。如果是这样,那么你显然需要创建自己的抽象来模拟你所请求的行为。滥用共享指针来做他们不应该做的事情是不正确的。

另外,请阅读this

修改
如果您要做的只是告知用户在崩溃之前发生了什么,那么考虑将Socket包装在另一个包装器对象中,该对象将在其销毁时调用删除器,捕获抛出的任何异常,通过向用户显示消息框或其他任何内容来处理它们。然后将此包装器对象放在boost::shared_ptr

答案 1 :(得分:4)

我们需要在某处存储已分配的资源(正如DeadMG已经提到的那样)并在任何析构函数之外显式调用某些报告/抛出函数。但这并不妨碍我们利用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&);
};

cleanupAndReport()的实现应该稍微复杂一点:在当前版本中,清理后池中填充空指针,如果抛出异常,我们必须调用该函数,直到它不再抛出等为止,但我希望,它很好地说明了这个想法。

现在,更一般的解决方案:

//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);

答案 2 :(得分:1)

引用Herb Sutter,“Exceptional C ++”的作者(来自here):

  

如果析构函数抛出异常,   坏事可能发生。特别,   考虑如下代码:

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

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

如果析构函数抛出异常   而另一个例外已经存在   有效(即在堆栈展开期间),   该计划终止。这是   通常不是一件好事。

换句话说,无论你在这种情况下想要相信什么是优雅的,你都不能轻易地在析构函数中抛出异常,除非你能保证在处理另一个异常时它不会被抛出。

此外,如果你不能成功摆脱资源,你能做什么?对于可以处理得更高的事情,而不是错误,应该抛出异常。如果要报告奇怪的行为,请记录发布失败并继续。或终止。

答案 3 :(得分:1)

正如问题所述,编辑3:

这是另一种解决方案,据我所知,它可以实现 问题中的要求。它类似于所描述的解决方案 在原始问题中,但使用boost::shared_ptr而不是a 自定义智能指针。

此解决方案的核心思想是提供release() shared_ptr上的操作。如果我们可以让shared_ptr放弃它 所有权,我们可以自由调用清理函数,删除对象, 如果在清理过程中发生错误,则抛出异常。

Boost有good reason 不在release()上提供shared_ptr操作:

  

shared_ptr不能放弃所有权,除非它是唯一的()因为   其他副本仍将销毁该对象。

     

考虑:

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.
     

此外,release()返回的指针很难   可靠地解除分配,因为可以创建源shared_ptr   使用自定义删除器。

反对release()操作的第一个参数是,通过 shared_ptr的性质,许多指针共享对象的所有权, 所以没有一个人可以简单地释放所有权。但是什么 如果有release()函数返回空指针 还剩下其他参考? shared_ptr可以可靠地确定 这,没有竞争条件。

反对release()操作的第二个参数是,如果a 自定义删除器已传递给shared_ptr,您应该使用它 取消分配对象,而不是简单地删除它。但是release() 除了原始指针之外,还可以返回一个函数对象 使其调用者能够可靠地释放指针。

但是,在我们特定的szenario中,自定义删除器不会是一个 问题,因为我们不必处理任意的自定义 删除者。从下面的代码中可以看出这一点。

release()上提供shared_ptr操作而不进行修改 当然,如果没有黑客攻击,它的实现是不可能的。该 在下面的代码中使用的hack依赖于线程局部变量 防止我们的自定义删除器实际删除该对象。

那就是说,这是代码,主要由标题组成 Resource.hpp,加上一个小的实施文件Resource.cpp。注意 它必须与-lboost_thread-mt链接,因为 线程局部变量。

// ---------------------------------------------------------------------
// 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

执行文件:

// ---------------------------------------------------------------------
// 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

以下是使用此解决方案实现的资源类的示例:

// ---------------------------------------------------------------------
// 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;
}

最后,这是一个构建所有内容的小Makefile:

# 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

答案 4 :(得分:0)

嗯,首先,我在这里看不到任何问题。其次,我不得不说这是一个坏主意。你会在这一切中获得什么?当销毁资源的最后一个共享指针并调用您的抛出删除器时,您会发现自己有资源泄漏。您将丢失无法释放的资源的所有句柄。你将永远无法再试一次。

您使用RAII对象的愿望很好,但智能指针根本不足以完成任务。你需要什么才能更聪明。你需要能够在完全崩溃的情况下重建自己的东西。析构函数不足以用于这样的接口。

您确实向自己介绍了滥用行为,因为有人可能会导致资源处理但无效。您在此处理的资源类型仅适用于此问题。有很多方法可以解决这个问题。一种方法可以是使用手柄/身体习语以及状态模式。接口的实现可以处于以下两种状态之一:已连接或未连接。句柄只是将请求传递给内部正文/状态。正常,未连接的连接工作会在所有适用的请求中抛出异常/断言。

这个东西除了〜之外还需要一个函数来销毁它的句柄。你可以考虑一个可以抛出的destroy()函数。如果在调用它时遇到错误,则不要删除句柄,而是以您需要的任何特定于应用程序的方式处理问题。如果你没有从destroy()中捕获错误,则让句柄超出范围,重置它或其他任何东西。函数destroy()然后记录资源计数并尝试释放内部资源,如果该计数为0.成功时句柄切换到未连接状态,失败时会生成一个可捕获的错误,客户端可以尝试处理但是离开处于连接状态的句柄。

编写这不是一件非常简单的事情,但是你想要做的事情,将异常引入破坏,根本不起作用。

答案 5 :(得分:0)

一般来说,如果资源的C风格的闭包失败,那么它就是API的问题,而不是代码中的问题。但是,我想要做的是,如果破坏失败,则将其添加到需要销毁/清理的资源列表中,以便稍后重新尝试,例如,当app退出时,定期或其他类似资源被销毁时,以及然后尝试重新毁灭。如果在任意时间遗留任何内容,请提出用户错误并退出。