1

在我的代码中,我使用HANDLEs from 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自动调用它。我还看到std::auto_ptr<someType>在 C++ 中有类似的东西可以解决分配给new.

但是,由于我不使用newand 因为HANDLEis just typedefed to be a void *,所以我想知道我应该如何使用std::auto_ptr<someType>. 不知何故,应该可以给它一个自定义删除函数(if (!CloseHandle(h)) { throw std::exception("closeHandle error"); })。创建一个类将是另一种方法,因为每当它的实例超出范围时都会调用析构函数。但是,为每件简单的事情开设一个班级只是过分了。

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

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

4

6 回答 6

6

想到的一个想法是将boost::shared_ptr自定义删除器一起使用。

于 2009-10-12T18:43:44.190 回答
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)你不应该从析构函数中抛出

auto_handle h;
if (!openHandleToSomething(arg1, arg2, &h.handle())) {
    throw exception("openHandleToSomething error"); // Now it is safe
}
于 2009-10-12T19:33:43.603 回答
2

1)不要使用auto_ptr<>。严重地。你不想要那些令人头疼的问题——因为它没有熟悉的复制语义,所以很容易出错。

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

3)我从来没有真正打扰过包装HANDLE,所以我不知道是否有任何令人惊讶的问题。如果有,我无法指出。我不会期待任何 - 这是一个不透明的值。但是,谁会期待一个令人惊讶的问题呢?毕竟,它们是惊喜。

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

于 2009-10-12T18:41:19.513 回答
1

std::auto_ptr不适合这种情况它有它的用途,但这不是其中之一。为了纠正 Greg D 提出的一个观点,问题auto_ptr与其说是缺乏指针语义,不如说是它相当奇怪的所有权语义——当你分配一个时,你不会得到指针的副本,而是指针的转移(即,受让人成为资源的新唯一所有者,而受让人不再拥有任何东西)。

不过,您确实想将句柄包装在一个类中。我已经这样做了很多次,而且效果很好。在执行此操作时,我没有遇到任何特别令人惊讶的事情,尽管这并不一定意味着很多——句柄在 Windows 中用于很多事情,其中​​一些可能很容易有一些奇怪的东西。

于 2009-10-12T19:00:55.823 回答
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";
    }
}
于 2009-10-12T19:29:11.413 回答
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);
于 2018-04-02T09:26:35.427 回答