【问题标题】:When we go for custom deleter instead of default deleter in case of unique_ptr?在 unique_ptr 的情况下,当我们使用自定义删除器而不是默认删除器时?
【发布时间】:2023-01-26 23:28:51
【问题描述】:

如上所述,我无法理解是否已经有默认删除器,那么在 unique_ptr 的情况下自定义删除器需要什么? 谁能通过举个简单的例子来解释这个?

【问题讨论】:

  • 它可以用于例如当您自己“手动”管理内存时,或者当您希望删除器执行默认操作以外的操作时,例如关闭文件(如果与您尝试管理的资源相关)。
  • std::unique_ptr 几乎可以用于任何 RAII 任务,而不仅仅是堆分配的内存。例如,如果您将它与资源句柄一起使用,它需要知道如何删除/清理资源。
  • 如果您想做new/delete以外的事情。 fopen/fclose 用于文件管理,或其他一些资源管理。 IIRC LibGuard 使用unique_ptr 自定义删除器来释放互斥锁。

标签: c++ oop pointers


【解决方案1】:

我使用自定义删除来快速包装 C-API,例如帮助我使用 OpenSSL:

namespace helper {
template<typename T>
struct Deleter;

template<>
struct Deleter<::BIO>
{
    void operator()(::BIO* p) const
    {
        // result used during debugging
        [[maybe_unused]] auto result = BIO_free(p);
    }
};

template<>
struct Deleter<::X509>
{
    void operator()(::X509* p) const
    {
        X509_free(p);
    }
};

template<>
struct Deleter<::PKCS12>
{
    void operator()(::PKCS12* p) const
    {
        PKCS12_free(p);
    }
};

template<>
struct Deleter<::EVP_PKEY>
{
    void operator()(::EVP_PKEY* p) const
    {
        EVP_PKEY_free(p);
    }
};

template<>
struct Deleter<STACK_OF(X509)>
{
    void operator()(STACK_OF(X509) * p) const
    {
        sk_X509_pop_free(p, X509_free);
    }
};

template<>
struct Deleter<STACK_OF(GENERAL_NAME)>
{
    void operator()(STACK_OF(GENERAL_NAME) * p) const
    {
        sk_GENERAL_NAME_free(p);
    }
};

template<>
struct Deleter<GENERAL_NAME>
{
    void operator()(GENERAL_NAME* p) const
    {
        GENERAL_NAME_free(p);
    }
};

template<typename T, typename D = Deleter<T>>
std::unique_ptr<T, D> wrapUnique(T* p, D deleter = {})
{
    return std::unique_ptr<T, D>{p, deleter};
}
}

而且我使用其他辅助函数来使 OpenSSL 更符合 C++sy。

【讨论】:

    【解决方案2】:

    这是一个使用std::unique_ptrRAII 风格管理旧C 风格FILE* 的示例。
    std::unique_ptr 尝试释放托管资源时,删除器将自动关闭文件。
    这很有用,例如如果您有一些遗留代码使用 FILE* 文件并且您希望避免手动管理关闭它们。

    #include <iostream>
    #include <memory>
    
    struct FileDeleter
    {
        void operator()(FILE* pFile)
        {
            if (pFile)
            {
                std::cout << "closing file ...
    ";
                fclose(pFile);
            }
        }
    };
    
    using FileUniquePtr = std::unique_ptr<FILE, FileDeleter>;
    
    int main()
    {
        {
            // Getting a FILE* from legacy code:
            FILE* pFile = nullptr;
            std::cout << "opening file ...
    ";
            auto err = fopen_s(&pFile, "aaa.txt", "r");
            if (err != 0)
            {
                // Handle error ...
                return -1;
            }
            
            // New C++ code:
            FileUniquePtr filePtr{ pFile };
            // Do something with the file via filePtr ...
    
        } // Here filePtr goes out of scope and the deleter will be called and close the file automatically.
    }
    

    输出:

    opening file ...
    closing file ...
    

    【讨论】:

      【解决方案3】:

      如果对 unique_ptr 包裹的指针执行 delete 是处理它的正确做法,则默认删除器是可以的。如果您的指针来自new,那没问题,但在许多其他情况下这是不正确的。

      一个简单的例子是FILE *FILE *是一个不能用delete删除的指针,但是你必须在它上面使用fclose。使用自定义删除器很容易将其包装在 unique_ptr 中并让它负责销毁/移动/...:

      namespace detail {
          struct file_ptr_deleter {
              void operator() (FILE *fp) {
                  if(fp) fclose(fp);
              }
          };
      }
      
      /// RAII-style wrapper for a FILE*
      struct unique_c_file : std::unique_ptr<FILE, detail::file_ptr_deleter> {
          using std::unique_ptr<FILE, detail::file_ptr_deleter>::unique_ptr;
          operator FILE *() { return get(); }
      };
      

      (在这种情况下,我什至继承自 std::unique_ptr 以简化直接在 C API 中使用 unique_c_file)。

      其他可能更常见的情况是,如果您有来自库的内存,它们会提供自己的删除功能;例如,您可以使用 BSD 套接字,其中 getaddrinfo 为您提供了一个必须使用 freeaddrinfo 释放的 addrinfo *;即使在这种情况下,也很容易为此创建一个智能指针:

      namespace detail {
          struct addrinfo_deleter {
              void operator()(addrinfo *elem) {
                  if(elem) freeaddrinfo(elem);
              }
          };
      }
      
      /// unique_ptr to handle an %addrinfo linked list provided by %getaddrinfo
      typedef std::unique_ptr<addrinfo, detail::addrinfo_deleter> addrinfo_ptr;
      

      类似地,在 Win32 编程中,许多 API 为您提供已使用 LocalAlloc 分配的内存,因此必须使用 LocalFree 释放内存:

          // Custom deleter (for unique_ptr) using Win32 LocalFree
          struct LocalFree_deleter { void operator()(void *ptr) { LocalFree(ptr); } };
          // ...
          std::unique_ptr<LPWSTR[], LocalFree_deleter> argvw(CommandLineToArgvW(cmdline_u16, &argc));
      

      您甚至可以更进一步,处理甚至不是指针的东西,而是不透明的“句柄”:同样在 Win32 中,这很常见,甚至不同类型的句柄需要不同的关闭函数;即使在这种情况下,unique_ptr 和自定义删除器也可以拯救:

      template<decltype(CloseHandle) ch_fn = CloseHandle>
      struct handle_deleter {
          typedef HANDLE pointer;
          void operator()(HANDLE obj) {
              if(obj != NULL && obj != INVALID_HANDLE_VALUE) ch_fn(obj);
          }
      };
      
      using unique_handle = std::unique_ptr<HANDLE, handle_deleter<>>;
      using unique_find_handle = std::unique_ptr<HANDLE, handle_deleter<FindClose>>;
      

      注意这里handle_deleter提供了一个typedef HANDLE pointer:这个typedefunique_ptr用作它存储在其内部的类型,在get() & co 中返回。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-03-30
        • 1970-01-01
        • 1970-01-01
        • 2023-03-11
        • 1970-01-01
        • 1970-01-01
        • 2015-08-13
        相关资源
        最近更新 更多