【问题标题】:C++ std::queue::pop() calls destructor. What of pointer types?C++ std::queue::pop() 调用析构函数。指针类型呢?
【发布时间】:2011-01-01 10:00:46
【问题描述】:

我有一个std::queue,它被包装为一个模板类以创建一个线程安全队列。我有这个类的两个版本:一个存储值类型,一个存储指针类型。

对于指针类型,我无法在销毁时删除队列中的元素。原因是我不知道如何安全地从队列中移除项目。

This 引用声明(空洞地,所以我猜它实际上并没有声明它)从队列中删除元素的唯一方法是调用 pop()。引用还说pop() 调用了该项目的析构函数。

嗯,这会导致我的指针类型出现问题,因为它们实际上可能指向聚合,也可能不指向聚合。如果其中一个指向聚合,它们都将指向聚合,但由于包装器是模板化的,因此无法保证我们正在处理哪种类型(聚合或非聚合)。

那么,当pop() 调用析构函数时,会发生什么?如何确保所有内容都被删除并正确释放内存?

最后,我的解决方案是使用旧版本的 GCC for ARM9。我无法控制这个。我知道有些库有智能指针和容器可以在这里提供帮助,但它们对我来说是禁区。

【问题讨论】:

  • “嗯,这会导致我的指针类型出现问题,因为它们实际上可能指向聚合,也可能不指向聚合”我不明白聚合与这里的任何东西有什么关系。

标签: c++ memory-management queue std


【解决方案1】:

指针本身实际上没有析构函数,因此在包含指针的队列上调用pop() 不会调用指针指向的对象的析构函数。

【讨论】:

  • 感谢您的回答。这就是我想知道的,如果我漫不经心的问题不清楚的话。
  • Greg,我很欣赏快速的回答并且仍然投赞成票,但我将接受更改为 Neil 的回答,因为它对正在发生的事情有更全面的解释。
  • 我同意。虽然思路很好:D。第一个标志应该是指针没有析构函数。
  • @Hassan 不,它没有析构函数,但编译器可以跟踪类型,因此如果我们谈论的是指向类型,则可以调用 delete。我认为这不太可能,但实际发生的才是最重要的。
  • 当您销毁指向Tsmart 指针时,编译器会调用T::~T(),例如std::auto_ptrstd::tr1::shared_ptr<T>。这几乎就是 smart 指针的定义。如果只是为了保持 C 兼容性,它不会对原始指针做这些事情。
【解决方案2】:

在线资源物有所值 - 获得适当的参考,例如 Josuttis's book。 pop() 不会“调用析构函数”——它只是通过调用 pop_front() 从队列适配器的底层表示(默认情况下为 std::deque)中删除一个元素。如果被弹出的东西有析构函数,会在被弹出的对象超出范围时使用,但队列类与它无关。

【讨论】:

  • 说“不是由 pop()”真的公平吗?这就像说 main 什么都不做,因为 main 只调用其他函数。但是 main 什么都做。
  • @Hassan:容器总是,当它们从容器中移除时总是销毁它们包含的对象。总是。包含的对象是用户定义的类型、整数、指针还是智能指针都没有关系。它被破坏了(或者什么也没有发生,如果它没有析构函数)。那么唯一的问题是销毁弹出的对象实际上做了什么。对于指针,它什么也不做。 newd 对象永远不能是容器的内容,无论是否“插入智能指针”。指向它的指针可能是容器的内容,但那是完全不同的东西。
  • 重新=短语 Neils cmets。当您弹出一个对象(从标准容器中)时,将在弹出的对象上调用析构函数。指针对象没有析构函数(它指向的可能有析构函数,但这不是存储在队列中的内容)。因此,指针对象将简单地从队列中移除。
  • @Neil:我刚刚意识到,这个关于什么叫什么的业务,可能是每个人(包括我)最终都用被动语态编写所有文档的原因,它出现了比玛土撒拉更老的 Flesch-Kincaid 同类。 “对象在 pop() 返回之前从底层容器中删除,结果是在 pop() 返回之前调用其析构函数(如果有的话)”与“pop() 从底层容器中删除对象,因此调用它的析构函数(如果有)”。
  • @Steve 我看不出“从底层容器中删除对象”有什么问题——在每种情况下重现析构函数语义的解释的想法令人恐惧。事实上,这就是 C++ 标准的编写方式。
【解决方案3】:

“如何确保所有内容都被删除并正确分配内存?”

如果您绝对必须在队列中存储指针,并且希望它们在poped 时自动释放,那么您需要一个存储指针的对象队列而不是指针队列,并在他们的析构函数中删除它。例如,您可以使用 shared_ptr 队列。 shared_ptr 不在标准库中,但它是 TR1 的一部分并且被广泛使用。

否则,调用者有责任删除对象:

T *off = q.front();
q.pop();
delete off;

总结是指向动态分配对象的指针容器有点尴尬。如果您可以设计您的程序,以便容器存储对象的副本,而不是指向动态对象的指针,那么就这样做。否则,您将负责资源所有权,而不是容器。 STL 容器对所有权一无所知,它们只是复制并销毁其value_type。复制和销毁指针对它们指向的对象没有任何作用。

【讨论】:

  • 我想过这个但拒绝了,因为我不知道模板类的未来用途。我不想为线程安全问题的用户 b/c 提供对队列的原始访问权限,并且我不能保证用户会使用您刚才提到的想法,所以我决定不这样做。
  • 如果你的类使用它的模板参数作为隐藏队列的值类型,那么它可能应该做容器做的事情:如果用户分配任何东西,那么用户必须安排最终释放它(例如,使用智能指针作为值类型)。例如,如果它是一个事件队列,并且用户坚持使用普通指针,那么他们也许可以在事件处理程序中释放对象。但这不是容器的问题,IMO 你不应该尝试编写专门处理指针的容器。
  • ... 诚然,这只是将问题推给下一个人。但是有充分的理由不要将容器与所有权混淆,尤其是有人可能想使用您的容器来存储指针,而不给您所有权(因为它们指向静态数据或其他东西)。我假设 boost::ptr_deque 可以用作队列的底层容器,因此您可以有两个模板参数(如队列),并让用户使用它来满足他们的需求。
  • 通常我会同意这个合理的逻辑,但为了我的直接使用,元素的删除发生在异常情况下。在我的课堂上,只有在调用析构函数时才会删除这些项目,这意味着这个特定子系统可能存在很大的故障。我基本上使用这个队列作为生产者/消费者的中间人,如果已经生产了物品,如果出现这么大的错误,它们需要立即妥善处理。否则,驱动程序(消费者)线程将继续运行,硬件方面的情况会很糟糕。
猜你喜欢
  • 2011-12-12
  • 1970-01-01
  • 2018-04-16
  • 2022-01-20
  • 2022-01-01
  • 2012-07-22
  • 2012-03-15
  • 2014-02-17
  • 1970-01-01
相关资源
最近更新 更多