【问题标题】:Memory-efficient custom deleter for std::unique_ptr?std::unique_ptr 的内存高效自定义删除器?
【发布时间】:2018-01-02 15:03:20
【问题描述】:

这可能有点特定于实现,但其中一些似乎是基本的。

我确定我一定在标准库中遗漏了一些东西。

问题是这样的:

我想实现一个std::unique_ptr,它的删除器是free()

[因为值是通过malloc()分配的]

当然,有很多关于如何做到这一点的选择,但是 (至少在 x86-64 的 g++ 4.8.4 中)它们似乎具有不同的内存使用影响。

例如: 方法一:

std::unique_ptr<char, std::function<void(void*)>> ptr_a(malloc(10), free);

然而, sizeof(ptr_a) == 40 字节(8 表示 void*,32 表示 std::function)

方法二:

std::unique_ptr<void, void (*)(void*)> ptr_b(malloc(10), free);

稍微好一点,因为 sizeof(ptr_b) == 16 个字节(8 个用于 void*,8 个用于裸函数指针])

方法三:

template <void (*T)(void*)>
class Caller {
 public:
  void operator()(void* arg) {
    return T(arg);
  }
};
std::unique_ptr<void, Caller<free>> ptr_c(malloc(10));`

此时,sizeof(ptr_c) == 8 字节(可能的最小值) - 但我必须引入一个几乎是纯样板的类(并且如图所示,易于模板化)。

这似乎是一个如此简单的模式 - STL 中是否有某些元素可以执行上述 Caller&lt;&gt; 的操作?

当然,在对普通类型调用 delete 时,默认情况下 g++ 确实确实显示为 free() - 但这似乎远不能由标准保证(如果没有别的,新/删除可能会从默认分配/释放重新定义函数,然后 default_delete 将调用替换删除)。

此外,还有其他情况,在纯 C 库中分配的某些对象的释放将通过简单的函数调用而不是删除器来实现。为了让 std::unique_ptr 正确有效地调用它们,必须将这样的分配/释放函数包装在类中似乎有些乏味 - 这让我觉得我错过了一些东西(大多数现代 C++ 规范的其余部分似乎是经过深思熟虑的)。

【问题讨论】:

    标签: c++ c++11 memory-management stl


    【解决方案1】:

    C++11 有一个 integral_constant 类型,它适用于不是整数的东西。在 C++14 中,有一个 constexpr 转换回该值。

    所以,在 C++14 中,我们可以这样做:

    std::unique_ptr<void, std::integral_constant<decltype(free)*, free>> ptr_c(malloc(10));
    

    这很尴尬。 (这取决于() 将在其左侧参数中考虑强制转换为函数指针这一事实。

    我们可以免费硬编码:

    using default_free = std::integral_constant<decltype(free)*, free>;
    std::unique_ptr<void, default_free> ptr_c(malloc(10));
    

    消除使用现场的一些噪音。

    在 C++17 中,我们可以编写一个助手:

    template<auto t>
    using val = std::integral_constant< std::decay_t<decltype(t)>, t >;
    

    给我们:

    std::unique_ptr<void, val<free>> ptr_c(malloc(10));
    

    在我看来这更干净。

    Live examples.

    我们可以用 C++11 编写我们自己的版本:

    template<class T, std::decay_t<T> t>
    struct val {
      constexpr operator T() noexcept const { return t; }
    };
    using default_free = val<decltype(free), free>;
    std::unique_ptr<void, default_free> ptr_c(malloc(10));
    

    【讨论】:

    • 哇,太酷了。我从来没有想过以这种方式使用std::integral_constant
    • @Quentin 一个缺点是它不适用于基于继承的覆盖。
    • 哇。疯狂的。所以澄清一下,这个想法是std::integral_constant&lt;decltype(free)*, free&gt; 代表静态值,即指向free 的函数指针,从而让我们将指向free 的指针作为析构函数而不需要指针大小的成本?如果这仍然增加了指针调用函数的成本,有什么意义吗?所以如果不是free 它是一个内联函数,它会内联吗?
    • 我在godbolt.org 尝试过,看起来如果您将integral_constant 用于内联函数,它实际上确实找到了该函数并将其内联。如果它不是内联的(只是一个函数声明),那么它会使用call 调用它。很酷。 godbolt.org/g/nGhPJR
    【解决方案2】:

    还有使用无状态 lambda 的第四个选项:

    auto free_lmbd = [](void *_ptr) { free (_ptr);};
    std::unique_ptr<void, decltype (free_lmbd)> ptr {malloc(10), free_lmbd};
    

    这也将有 8 个字节(至少在我的计算机上),与您的第三个选项相同。

    我推荐阅读http://www.bfilipek.com/2016/04/custom-deleters-for-c-smart-pointers.html

    【讨论】:

    • 这个对象变得难以传递而不违反编译单元之间的 ODR。
    猜你喜欢
    • 2013-03-30
    • 1970-01-01
    • 1970-01-01
    • 2015-04-09
    • 1970-01-01
    • 2017-05-15
    • 2018-01-31
    • 2012-04-11
    • 1970-01-01
    相关资源
    最近更新 更多