【问题标题】:Making a non-object resource RAII-compliant使非对象资源符合 RAII
【发布时间】:2009-10-12 18:35:18
【问题描述】:

在我的代码中,我使用来自windows.hHANDLEs。它们被用作

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 只是typedefed 成为void *,我想知道我应该如何使用std::auto_ptr<someType>。不知何故,应该可以给它一个自定义删除函数(if (!CloseHandle(h)) { throw std::exception("closeHandle error"); })。创建一个类将是另一种方法,因为每当它的实例超出范围时都会调用析构函数。但是,为每件简单的事情都设置一个类就有点过头了。

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

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

【问题讨论】:

    标签: c++ raii auto-ptr


    【解决方案1】:

    想到的一个想法是使用boost::shared_ptrcustom deleter

    【讨论】:

    • 是的,这是 shared_ptr 的设计目标之一,并且做得很好。这绝对是最好的解决方案(尽管我通常避免这样的绝对)。
    • @dalle:啊,感谢您添加链接。这是一个比我更好的例子。
    • 智能指针是这个问题的错误解决方案。你需要的是一个简单的包装器。
    • 已经找到了。需要 SP1 来更新 文件。没有 sp1 你就没有它。
    • @Fred:我们显然不同意什么是复杂性。但是共享指针显然是错误的智能指针。充其量这应该是一个作用域指针。不需要通过代码的任何其他部分传递或共享所有权。它在一个只需要异常安全的地方创建和销毁。
    【解决方案2】:

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

    【讨论】:

    • 如果您复制句柄,我认为您会遇到问题。每个副本都会尝试关闭同一个句柄。
    • handle_() 和 handle_(h) 是什么?句柄只是一个void*,如何调用它的函数?
    • 似乎它至少需要默认构造函数,因为否则它不会编译。但是,它似乎不起作用。我只是收到一个编译器错误,因为它无法从 'auto_handle *' 转换为 'PHANDLE'
    • 为什么复制 ctor 是私有的/未实现的? DuplicateHandle 存在是有原因的。注意:复制 ctor 因异常而失败是可以的。
    • 该类旨在保留句柄的所有权并在超出范围时关闭它。私有复制构造函数是为了避免它被复制或复制。那件事不在问题范围内,并且意味着引用计数(或 DuplicateHandle,我不知道)。
    【解决方案3】:

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

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

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

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

    【讨论】:

    • 您的第 1 点不正确。 auto_ptr 具有指针语义,并且有其用途(由 C++0x 的右值引用改进)。但是,它根本无法用作解决此问题的方法,因为它缺少所需的功能。
    • @fnieto:谁说过祖先的事?
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:
        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);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-18
          • 1970-01-01
          • 2011-01-29
          • 2012-07-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多