【问题标题】:shared_ptr with deleter class - why deleter is copied?带有删除器类的 shared_ptr - 为什么要复制删除器?
【发布时间】:2020-04-10 14:26:05
【问题描述】:

假设我使用自定义删除器创建了一个共享指针。在下面的代码中,我想检查删除器对象本身会发生什么:

struct A {
    A() { std::cout << "A\n"; }
    ~A() { std::cout << "~A\n"; }
};

struct D {
    D() {
        std::cout << "D\n";
    }
    ~D() {
        std::cout << "~D\n";
    }
    D(const D&) {
        std::cout << "D(D&)\n";
    }
    void operator()(A* p) const {
        std::cout << "D(foo)\n";
        delete p;
    }
};

int main()
{
    std::shared_ptr<A> p(new A, D());
}

我看到“删除器”类的D(const D&amp;)~D() 又被调用了六次:

D
A
D(D&)
D(D&)
D(D&)
D(D&)
D(D&)
D(D&)
~D
~D
~D
~D
~D
~D
D(foo)
~A
~D

会发生什么?为什么要复制这么多次?

【问题讨论】:

  • 我懒得看实际的实现(你使用的是哪个库?)但是删除器可能是通过值传递的,因为它应该是一个简单的函数指针(或类似的),因此复制起来很便宜。这仍然是一个有趣的问题:我希望会有一些副本,但可能不会那么多。
  • 你的问题是正确的,但如果你关心性能,可能你根本不应该使用shared_ptr,因为它是性能方面最差的智能指针。 weak_ptr 是使用 shared_ptr 的唯一理由。

标签: c++ shared-ptr copy-constructor


【解决方案1】:

删除器通常是一个带有operator() 的空类。在优化的代码和编写良好的shared_ptr 实现中,空类的删除器不占用空间,因此复制开销为零。

在优化器完成工作后,实现通常会知道围绕对象进行复制是否代价高昂,以及是否必须采取预防措施。

在本例中,我猜您正在观察未优化的构建。您会看到实现如何通过多层函数调用传递删除器。在优化的构建中,实际上不会复制任何内容,因为删除器类 D 为空。

【讨论】:

  • “实际上什么都不会被复制,因为删除器类 D 是空的”——我想不出类为空会影响优化器决定是否复制的情况(在事实上,如果情况正好相反:避免大对象的昂贵副本比空对象更值得)。虽然一般可以省略复制,但对于 OP 来说却不是这样:-O2 不会更改 clang 或 GCC 的输出。
  • @KonradRudolph 不是决定是否复制;事实上,当删除器是一个空类时,什么都没有可以复制。当没有什么可复制的时候,它必须复制多少次都没关系。
  • 问题是它仍在调用复制构造函数,即使类是空的。 是昂贵的部分,如果没有默认复制构造函数。事实上,这就是标准在某些情况下明确允许复制省略的原因。您的回答推测 OP 看到的是未优化的构建。我具体告诉你,在优化的构建中观察到相同的结果。尽管类是空的,但这里不会发生复制删除(因为,正如我所说,一个类是否为空与是否删除副本无关)。
【解决方案2】:

创建自定义删除器很有趣,但另一方面它缺乏优化,因为定义明确的共享指针将运行以实现删除功能。 (+新)

如果您查看&lt;memory&gt; 中 shared_ptr 的实现,您可以了解如何准备删除器。

请注意,智能指针的实现因编译器和使用的库而异。 (例如你的代码给了我 3 个副本)

仍在调查为什么会发生这种情况,但是是的,weak_ptr 是更好的选择,因为它打破了由 shared_ptr 管理的对象形成的引用循环。

【讨论】:

    【解决方案3】:

    我用 gcc 7.4 检查了你的代码,我得到了相同数量的析构函数调用。您观察到删除器对象通过std::move(deleter) 移动了六次。

    由于您在类中添加了析构函数,默认移动语义的自动生成被禁用,您需要明确定义它们:

    D(D&&) = default;
    D& operator=(D&&) = default;
    

    但是,即使使用移动语义,析构函数仍然可以调用多达六次。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-19
      • 1970-01-01
      • 2015-11-30
      相关资源
      最近更新 更多