使非对象资源符合RAII标准

时间:2009-10-12 18:35:19

标签: c++ raii auto-ptr

在我的代码中,我使用HANDLE中的windows.h。它们像

一样使用
HANDLE h;
if (!openHandleToSomething(arg1, arg2, &h)) {
    throw std::exception("openHandleToSomething error");
}
/* Use the handle in other functions which can throw as well */
if (!CloseHandle(h)) {
    throw std::exception("closeHandle error");
}

如您所见,您必须将此CloseHandle插入到可能在获取和释放过程中发生的每个异常。因此,你可能会忘记一个(或者有一个你不知道的奇特的SEH例外)并且呃,你的内存泄漏了。

最近,我已经阅读了有关RAII的内容,它应该可以消除此类案件中的麻烦,并应自动调用此CloseHandle。我还看到C ++中有类似std::auto_ptr<someType>的内容,它解决了使用new分配的资源问题。

但是,由于我不使用new,因为HANDLE只是typedef编辑为void *,我想知道我应该如何使用std::auto_ptr<someType> }}。不知何故,应该可以给它一个自定义删除函数(if (!CloseHandle(h)) { throw std::exception("closeHandle error"); })。创建一个类将是另一种方法,因为析构函数在其实例超出范围时被调用。然而,为每一件简单的事情设一个课程真是太过分了。

如何修复这些意外内存泄漏?

请注意,我更喜欢纯C ++中没有库和大依赖关系的解决方案,除非它们非常小并且无论如何都要在大多数环境中使用。

6 个答案:

答案 0 :(得分:6)

我想到的一个想法是将boost::shared_ptrcustom deleter一起使用。

答案 1 :(得分:3)

您可以实现自己的简单RAII习惯用法。

class auto_handle {
public:
    auto_handle() : handle_() {}
    ~auto_handle() {
        if (!CloseHandle(handle_)) {
            // Don't throw here (1), manage the error in other way.
        }
    }
    HANDLE& handle() { return handle_; }
private:
    auto_handle(const auto_handle&);
    auto_handle& operator=(const auto_handle&);
    HANDLE handle_;
};

(1)You should never throw from a destructor

auto_handle h;
if (!openHandleToSomething(arg1, arg2, &h.handle())) {
    throw exception("openHandleToSomething error"); // Now it is safe
}

答案 2 :(得分:2)

1)不要使用auto_ptr<>。认真。你不想要那些令人头疼的问题 - 它很容易滑倒b / c它没有熟悉的复制语义。

2)用一个简单的对象包装HANDLE,该对象提供一个访问器,为您提供底层句柄。您需要将HANDLE传递给API调用。 (我认为访问器比隐式转换更可取。)

3)我从来没有真正打扰包裹HANDLE,所以我不知道是否有任何令人惊讶的问题。如果有,我不能指出它们。我不指望任何 - 这是一个不透明的价值。但那时,谁预计会出现令人惊讶的问题?毕竟,他们是惊喜。

4)(当然)实施适当的dtor。

答案 3 :(得分:1)

std::auto_ptr 适合这种情况。它有它的用途,但这不是其中之一。为了纠正(排序)Greg D提出的一个问题,auto_ptr的问题不在于它缺少指针语义,因为它相当奇怪的所有权语义 - 当你指定一个时,你没有获得指针的副本,而是指针的传输(即受让人成为新的唯一所有者资源,分配者不再有任何东西)。

您确实希望将句柄包装在一个类中。我已经做过很多次了,而且效果很好。在做这件事的时候我没有遇到任何特别令人惊讶的事情,虽然这并不一定意味着很多 - Windows中的很多东西都使用了句柄,其中一些可能很容易产生一些奇怪之处。

答案 4 :(得分:1)

你只需要一个简单的包装器,在你将它传递给函数时为你提供句柄:

#include <stdexcept>
class HWrapper
{
    HANDLE h;
    bool   closed;

    public:
        HWrapper(A1 arg1,A2 arg2)
            :closed(false)
        {
            if (!openHandleToSomething(arg1, arg2, &h))
            {    throw std::runtime_error("openHandleToSomething error");
            }
        }
        ~HWrapper()
        {
            try
            {
                if (!closed)
                {   close();
                }
            }
            catch(...) {/*Exceptions should never leave a destructor */ }
            // Though you may want to log somthing.
        }
        void close()
        {
            closed = true;
            // Close can throw an exception.
            if (!CloseHandle(h))
            {    throw std::runtime_error("closeHandle error");
            }
        }

        /*
         * This allows you to just pass it to a function that takes an HANDLE
         * See the function:   functionThatUsesHandleButMayThrow();
         */
        operator HANDLE()
        {
            return h;
        }
    private:
    /*
     * For your use case there is not need to copy.
     * So explicitly disallow copying.
     *
     * Just pass the HWrapper object to any function that requires a handle.
     * The built in cast operator will convert it back to a Handle to be used
     * within these functions. While this object just retains ownership and
     * responcability for deleting the object when you are finished.
     *
     * This allows simple backwards compatibility with existing code.
     */ 
    HWrapper(HWrapper const& copy);            // Don't implement
    HWrapper& operator=(HWrapper const& copy); // Don't implement


};

void functionThatUsesHandleButMayThrow(HANDLE h)
{
}



int main()
{

    try
    {
        HWrapper   w(A1,A2);

        functionThatUsesHandleButMayThrow(w);
        /*
         * If you do not care about close throwing an excepion.
         * Then jsut let it fall out of scope. The destructor
         * will try and clean up. But if it fails it will drop the
         * exception.
         *
         * This is required because if another exception is propogating
         * throwing an exception terminates the application.
         */
    }
    catch(std::exception const& e)
    {
        std::cout << "Exception: " << e.what() << "\n";
    }


    try
    {

        HWrapper   w2(A1,A2);

        functionThatUsesHandleButMayThrow(w2);
        /*
         * If you do care abou the exception
         * The call close() manually. The exception will be thrown.
         *
         * But if an exception is already been thrown in
         * functionThatUsesHandleButMayThrow() then we will try and close it
         * in the destructor and not throw another exception.
         */
        w2.close();
    }
    catch(std::exception const& e)
    {
        std::cout << "Exception: " << e.what() << "\n";
    }
}

答案 5 :(得分:0)

HANDLE h;
if (!openHandleToSomething(arg1, arg2, &h)) {
    throw std::exception("openHandleToSomething error");
}

这是:

auto d = [](decltype(h)* a) { if(a) ::CloseHandle(*a); };
std::unique_ptr<decltype(h), decltype(d)> buf(&h, d);