从列表中弹出的对象何时被销毁

When are objects popped from a list destroyed?

本文关键字:何时 对象 列表      更新时间:2023-10-16

这可能是一个非常愚蠢/基本的问题,但我真的弄不明白。我告诉你我的想法,如果我错了,请纠正我。

当我们使用STL容器存储原始指针时,我们必须注意在它们之后进行清理:

std::list<animal*> alist;
animal *a = new animal();
alist.push_back(a);
...
animal *b = alist.front();
alist.pop_front();
//do stuff with b
delete b;

如果我们存储对象会发生什么?我的理解是,如果一个装满对象的容器超出了作用域,那么其中的所有对象都将被销毁。正确吗?

但是,如果我们从容器中删除一个对象,例如使用std::list<T>.pop_front()呢?

std::list<animal> alist;
{
  animal ani();
  //ani is inserted at the end of the list (it IS ani rather than a copy 
  //since push_back accepts const T&)
  alist.push_back(ani);
} //nothing is deallocated/destroyed here
...
{
  animal b = alist.front(); //b is now a copy(?!) of the front element
                            //created via copy constructor
  alist.pop_front(); //the front element of the list is 
                     //destroyed/destructor is called 
  //do stuff with b
} //b goes out of scope

当您在容器中存储内容时,无论您传递什么都将被复制到容器1中。你仍然拥有原始对象2;容器拥有对象的副本。的确,push_back(举个例子)通过引用获取它的参数。这意味着当您将要推入的对象传递到容器中时,不需要复制作为参数本身使用,但是放入容器中的内容仍然是您告诉它要推入的内容的副本。通过引用传递意味着只需要一个副本;如果它通过值传递,(至少在c++ 98/03中)将导致两个副本:一个从对象复制到函数实参,然后另一个从函数实参复制到容器。通过引用传递允许将原始对象的一个副本直接复制到容器中。

同样地,当你从容器中获取一个对象时,你得到的是容器中内容的副本。容器仍然有它的对象,你也有你的对象。它们都有自己的生命周期。

当你从容器中擦除或弹出一个对象时,该对象将从容器中移除并销毁——如果它是具有析构函数的对象,则将调用析构函数来销毁它。

当容器本身被销毁时,容器中的对象也将被销毁。无论何时,对象都将被销毁——它来自容器的事实对此没有影响。如果它是一个局部变量,当它超出作用域时,它将被销毁。如果它是一个全局变量,它将持续到时间结束(对于程序)。如果您从函数返回它,并且它用于初始化引用,则将遵守延长其生命周期的正常规则。底线:在这一点上,它只是另一个对象,将有一个生命周期,就像任何其他对象以同样的方式定义。

这可能会有点…模糊当/如果你在容器中存储指针(原始或智能)。以上所有内容实际上都是正确的,但是被复制/存储的对象是一个指针。除非你使用一些容器"意识到"你正在存储指针(例如,Boost ptr_vector),否则它只是处理指针,并且"忽略"指针指向的某个对象的事实。


    在c++ 11中,你可以使用,例如,emplace_back在容器中构造一个对象,但同样的基本思想也适用——容器有一个它拥有的对象,你永远不会直接接触。
  1. 这里我使用"对象"更像一个非程序员——只是意味着"一些东西",不一定是一个类的实例。它甚至可能不是C语言使用这个词的意义上的"对象"——它可能是一个纯右值(例如,push_back(10))。

是。如果你存储的是对象而不是指针指向动态内存,那么你就不需要做任何的内存释放。
当你弹出元素时。标准库会注意调用弹出对象的析构函数。

规则是:
如果您分配了一些东西(称为new ),您只需要deallocate(称为delete )。

我的理解是,如果一个装满对象的容器超出了作用域,那么它所包含的所有对象也都超出了作用域。正确吗?

首先,一些重要的迂腐。对象不会超出作用域,但标识符会。这是一个重要的区别,它可能有助于澄清这里发生了什么:

当容器对应的标识符(变量)超出作用域时,相应的对象(容器本身)将被自动删除。然后,它的析构函数依次删除它的每个元素。

但是如果我们从容器中删除一个对象,例如使用std::list<T>.pop_front()呢?

容器只是删除所包含的元素(就您而言,它调用其析构函数)。(也可能有其他的内务处理,比如在std::vector中将所有剩余的元素按一个顺序排列,这会导致大量的析构函数/复制构造函数调用)。

std::list<animal> alist;
// A list is created in automatic storage (the stack).  Its elements
// will exist in the free store (the heap)
{
  animal ani;
  // ani is created in automatic storage (the stack)
  alist.push_back(ani);
  // A copy of ani (using animal(const animal&) ctor) is created in
  // the list "alist" via memory allocated on the free store (the heap)
} // ani.~animal() is called, then the storage for ani is recycled
// (ie, the stack space that ani used can be reused)
{
  animal b = alist.front();
  // either animal(animal&) or animal(animal const&)
  // is called on the previous line, constructing an instance
  // of animal called "b" in automatic storage (the stack)
  // This is a copy of a copy of the animal instance called "ani"
  // above, assuming nothing else besides this code has manipulated
  // alist
  alist.pop_front();
  // The animal in the alist that is a copy of ani is destroyed
  // from the free store (the heap)

  //do stuff with b
} // b.~animal() is called, then the memory in automatic storage
// (the stack) that b lived in is recycled for other purposes

如果从STL容器中删除对象,则如果它是结构/类类型,则调用其析构函数。同样取决于容器,用于存储对象的内部数据结构也会被释放/销毁。

请记住,指针类型不是结构/类类型,因此没有可以管理所指向的内存的析构函数。因此,如果您希望在从STL容器中删除指针时避免意外的内存泄漏,那么最好使用智能指针类型,例如std::shared_ptr<T>,它将正确管理分配给指针的内存,并在不再引用已分配的内存对象时释放它。