使用自定义删除器增强智能指针

boost smart pointer with custom deleter

本文关键字:增强 智能 指针 删除 自定义      更新时间:2023-10-16

我可以理解boost::shared_ptr在调用自定义删除器函数之前不会验证NULL,但是我该如何实现呢?这将帮助我避免为fclose或任何没有(正确)指定行为的函数编写愚蠢的包装器。

我的助推器:#define BOOST_VERSION 104500。这不是 11 C++(因为我使用 boost)。

该问题与:使shared_ptr不使用删除

有关

示例代码:

static inline
FILE* safe_fopen(const char* filename, const char* mode)
{
      FILE* file = NULL;
      (void)fopen_s(&file, filename, mode);
      return file;
}
static inline
void safe_fclose(FILE* file)
{
      if (file)
         BOOST_VERIFY(0 == fclose(file));
}
...
boost::shared_ptr<FILE> file( safe_fopen(FILE_DOWNLOAD, "rb"), safe_fclose);
...
//    now it is created => open it once again
file.reset( safe_fopen(FILE_DOWNLOAD, "rb"), safe_fclose);

编辑

我的问题最初是关于使用shared_ptr的第二部分:为什么提供删除器作为函数参数而不是模板参数?显然,答案就在这里:为什么unique_ptr采用两个模板参数,而shared_ptr只采用一个模板参数?C++ 11 的答案是unique_ptr,但为什么 boost 没有提供一个 - 我们永远不会知道。

这似乎是关于empty(非拥有)shared_ptr和null -ed之间的区别。

空的将(并且应该)调用删除器(对于特定类型的资源句柄,这可能意味着某些内容。事实上,值"0"甚至可能并不特别)。

空的将调用删除器,但带有nullptr_t参数。因此,您通常可以制作一个包装器来包装任何临时删除器(无论是内联 lambda 还是像 &::free 这样的函数指针):

template <typename F>
OptionalDeleter<F> make_optional_deleter(F&& f) { return OptionalDeleter<F>(std::forward<F>(f)); }
int main() {
    auto d = make_optional_deleter([](void*){std::cout << "Deletingn";});
    using namespace std;
    {
        shared_ptr<int> empty(std::nullptr_t{}, d);
    } // deleter not called, empty
    {
        shared_ptr<int> thing(static_cast<int*>(nullptr), d);
    } // deleter called, thing not empty
}

将打印

Empty, not deleting
Deleting

看起来你得到了非常"纯粹"的、忠实的、将原始意图传递给底层 API。这是一个好事™,因为如果shared_ptr<>"任意"例外,那么在删除"NULL"(或默认值)值有意义且不应跳过的情况下,它将变得不可用。

现在,选择权在你手中,理应如此。

当然,您可以使用非常相似的包装策略,就像我刚刚展示的那样,如果资源句柄值为"NULL"或其他无效,则通常跳过底层 API 调用。

住在科里鲁

#include <memory>
#include <iostream>
template<typename F>
struct OptionalDeleter final {
    explicit OptionalDeleter(F&& f) : _f(std::forward<F>(f)) {}
    template <typename... Ts>
        void operator()(Ts&&... args) const { _f(std::forward<Ts>(args)...); }
    void operator()(std::nullptr_t)   const { std::cout << "Empty, not deletingn"; }
  private:
    F _f;
};
template <typename F>
OptionalDeleter<F> make_optional_deleter(F&& f) { return OptionalDeleter<F>(std::forward<F>(f)); }
int main() {
    auto d = make_optional_deleter([](void*){std::cout << "Deletingn";});
    using namespace std;
    {
        shared_ptr<int> empty(std::nullptr_t{}, d);
    } // deleter not called, empty
    {
        shared_ptr<int> thing(static_cast<int*>(nullptr), d);
    } // deleter called, thing not empty
}

一个想法可能是,与其试图保护析构函数,不如在构造时强制失败,从而避免在销毁过程中检查无效。

static inline
FILE* safe_fopen(const char* filename, const char* mode)
{
      FILE* file = NULL;
      (void)fopen_s(&file, filename, mode);
      if (!file)
         throw std::exception(...); // check errno
      return file;
}

但是,如果由于某种原因仍然发生未初始化的boost::shared_ptr,这无助于修复。

与不使用删除不同shared_ptr我认为由于函数的性质,您只是坚持使用这些更长的包装器。