使非对象资源符合 RAII 标准

发布于 2024-08-07 17:38:33 字数 1055 浏览 9 评论 0原文

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

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 的东西,它解决了用 new 分配的资源的问题。

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

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

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

in my code I use HANDLEs from windows.h. They are used like

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");
}

As you see, you have to insert this CloseHandle to every exception which can happen in the middle of acquiration and release. Therefore, it's likely you forget one (or there is a fancy SEH exception which you didn't know about) and voilà, you have your memory leak.

Recently, I've read about RAII which should remove the headaches from such cases and should call this CloseHandle automatically. I've also seen that there is something like std::auto_ptr<someType> in C++ which solves the problem for resources which were allocated with new.

However, since I don't use new and since HANDLE is just typedefed to be a void *, I wonder how I should use the std::auto_ptr<someType>. Somehow, it should be possible to give it a custom deleter function (if (!CloseHandle(h)) { throw std::exception("closeHandle error"); }). Creating a class would be another method since the destructor gets called any time an instance of it gets out of scope. However, it's just overkill to have a class for every simple thing.

How can I fix these accidential memory leaks?

Note that I would prefer solutions which are in pure C++ with no libraries and big dependencies except if they are really small and used in most of the environments anyways.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

瑾兮 2024-08-14 17:38:33

我想到的一个想法是使用 boost::shared_ptr< /a> 带有自定义删除器

One idea that comes to mind is to use boost::shared_ptr with a custom deleter.

ぃ弥猫深巷。 2024-08-14 17:38:33

您可以实现自己的简单 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
}

You can implement your own simple RAII idiom.

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
}
关于从前 2024-08-14 17:38:33

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

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

3)我从来没有真正费心包装 HANDLE,所以我不知道是否有任何令人惊讶的陷阱。如果有的话,我也无法指出。我不期望任何——这是一个不透明的值。但是,谁会想到一个令人惊讶的陷阱呢?毕竟,它们是惊喜。

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

1) Don't use auto_ptr<>. Seriously. You don't want those headaches-- it's far too easy to slip up b/c it doesn't have familiar copy semantics.

2) Wrap HANDLE with a simple object that provides an accessor that gives you the underlying handle. You'll need this to pass the HANDLE in to API calls. (I'd consider an accessor preferable to an implicit conversion.)

3) I've never actually bothered wrapping HANDLE, so I don't know if there are any surprising gotcha's. If there are, I can't point them out. I wouldn't expect any-- it's an opaque value. But then, who expects a surprising gotcha'? They're surprises, after all.

4) (Of course) implement the appropriate dtor.

人间不值得 2024-08-14 17:38:33

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

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

std::auto_ptr is not suitable for this situation. It has its uses, but this isn't one of them. To correct (sort of) a point raised by Greg D, the problem with auto_ptr isn't so much its lack of pointer semantics, as its rather odd ownership semantics -- when you assign one, you don't get a copy of the pointer, but instead a transfer of the pointer (i.e the assignee becomes the new sole owner of the resource, and the assigner no longer has anything).

You do want to wrap the handle in a class though. I've done this a number of times, and it works quite well. I haven't run into anything particularly surprising when doing it, though that doesn't necessarily mean a lot -- handles are used for a lot of things in Windows, and some of them might easily have some oddities.

临走之时 2024-08-14 17:38:33

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

#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";
    }
}

You just want a simple wrapper that gives you the handle when you pass it into a function:

#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";
    }
}
海风掠过北极光 2024-08-14 17:38:33
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);
HANDLE h;
if (!openHandleToSomething(arg1, arg2, &h)) {
    throw std::exception("openHandleToSomething error");
}

Here it is :

auto d = [](decltype(h)* a) { if(a) ::CloseHandle(*a); };
std::unique_ptr<decltype(h), decltype(d)> buf(&h, d);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文