【问题标题】:Is there a convenient way to get unique_ptr to automagically have a deleter like shared_ptr?有没有一种方便的方法可以让 unique_ptr 自动拥有一个像 shared_ptr 这样的删除器?
【发布时间】:2014-10-05 04:55:59
【问题描述】:

std::shared_ptr 有一个漂亮的模板化构造函数,它会自动为其给定类型创建正确的删除器(该链接中的构造函数 #2)。

直到刚才,我(错误地)认为std::unique_ptr 有一个类似的构造函数,但是当我运行以下代码时:

#include <memory>
#include <iostream>

// Notice nothing is virtual
struct Foo
{
    ~Foo() { std::cout << "Foo\n"; }
};

struct Bar : public Foo
{
    ~Bar() { std::cout << "Bar\n"; }
};

int main()
{
    {
        std::cout << "shared_ptr:\n";
        std::shared_ptr<Foo> p(new Bar()); // prints Bar Foo
    }

    {
        std::cout << "unique_ptr:\n";
        std::unique_ptr<Foo> p(new Bar()); // prints Foo
    }
}

I was surprised to learn that unique_ptr doesn't call Bar's destructor.

创建一个unique_ptr 并为其给定指针具有正确的删除器的干净、简单和正确的方法是什么?特别是如果我想存储这些的完整列表(即std::vector&lt;std::unique_ptr&lt;Foo&gt;&gt;),这意味着它们都必须具有异构类型?

(请原谅这个糟糕的标题;欢迎提出更好的标题)

【问题讨论】:

  • unique_ptr 的删除器在它的类型中。如果你做一个std::unique_ptr&lt;Foo&gt;的向量,那么他们都将使用std::default_delete&lt;Foo&gt;
  • 我认为问题出在你的班级,而不是std::unique_ptr。你的析构函数必须是虚拟的。

标签: c++


【解决方案1】:

你应该创建Foovirtual的析构函数。无论您是否使用unique_ptr,这都是一种很好的做法。这也将解决您正在处理的问题。

【讨论】:

  • 我同意这是应该做的,但在这种情况下我无法更改课程
【解决方案2】:

这是一种方法:

{
    std::cout << "unique_ptr<Bar, void(void*)>:\n";
    std::unique_ptr<Foo, void(*)(void*)> p(
        new Bar(), [](void*p) -> void { delete static_cast<Bar*>( p ); }
        ); // prints Bar Foo
}

这种方法的一个主要问题是unique_ptr 支持转换为逻辑“指向基类的指针”,但该标准不保证转换为void* 将产生相同的地址。实际上,如果基类是非多态的,而派生类是多态的,这只是一个问题,引入了一个 vtable ptr,因此可能会稍微改变内存布局。但是在那种可能但不太可能的情况下,删除器中的强制转换会产生不正确的指针值,然后爆炸。

因此,对于此类转换,上述内容在形式上并不安全。


要与shared_ptr 大致相同(shared_ptr 支持转换为逻辑指针到基地址),您还需要存储原始 void* 指针以及删除器。


通常,当您控制最顶层的基类时,将其析构函数设为虚拟。

一切都会好起来的。

【讨论】:

  • 对于有问题的场景,如何将static_cast 加倍——先将void * 转换为Foo *,然后再转换为Bar *
  • @T.C.问题是删除器不知道传递给void* 的指针类型。根据类型,它可以指向不同的地址。但是现在你让我想到它,问题只是void* 丢弃了太多信息。我认为使用Foo* 参数类型一切都会好起来的,然后在删除器中将其转换为Bar*。或者,这有什么问题吗?这对我来说有点晚了。
  • 传递给删除器的指针并不总是有用的,即使类型没问题。有时 static_cast 可能是不可能的,例如使用虚拟继承 (stackoverflow.com/questions/7484913/…)。我认为唯一真正强大的解决方案是让删除器记住原始指针值(从new 返回)。这意味着删除器正式忽略了它的参数,因此它也可能是void*。最后,一旦您支付了这个内存“价格”,您还可以免费添加deep_copy
猜你喜欢
  • 2020-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-16
相关资源
最近更新 更多