STL容器是如何被删除的?

7

STL中的容器对象(如vector)是在堆上创建的,那么它们如何被销毁呢?

编辑

如果容器持有指针,则如何销毁这些指针对象?

12个回答

20

STL容器中存储指针类型的数据时,仅会清理指针所占用的空间,而不会清理指针所指向的数据。如果您希望容器可以清理指针所指向的数据,则需要使用某种智能指针实现:

{
    std::vector<SomeClass*> v1;
    v1.push_back(new SomeClass());

    std::vector<boost::shared_ptr<SomeClass> > v2;
    boost::shared_ptr<SomeClass> obj(new SomeClass);
    v2.push_back(obj);
}

当作用域结束时,两个向量将释放其内部数组。v1将泄漏创建的SomeClass,因为只有指向它的指针在数组中。v2不会泄露任何数据。


5
如果你有一个vector<T*>,在删除向量之前,你的代码需要删除这些指针:否则,该内存将被泄漏。
请注意,C++不会进行垃圾回收,以下是为什么的示例(由于语法错误已经很长时间没有写过C++,敬请谅解):
typedef vector<T*> vt;
⋮
vt *vt1 = new vt, *vt2 = new vt;
T* t = new T;
vt1.push_back(t);
vt2.push_back(t);
⋮
delete vt1;

最后一行代码(delete vt1;)显然不应该删除指针所指向的内容,毕竟它也在vt2中。所以它并没有被删除,vt2同样不会被删除。
(如果你想要一个在销毁时删除指针的向量类型,这样的类型当然可以编写。可能已经有了。但要注意不要删除其他人仍然持有副本的指针。)

1
是的,它有:boost::ptr_vector。(请参阅boost ptr容器) - Martin York

3
当向量超出范围时,编译器会调用其析构函数,从而释放在堆上分配的内存。

3

这有点名不副实。就像大多数STL容器一样,向量由两个逻辑部分组成。

  1. 向量实例
  2. 实际的底层数组实现

虽然可配置,但#2几乎总是存在于堆上。但是#1可以存在于堆栈或堆上,这取决于它是如何分配的。例如:

void foo() { 
  vector<int> v;
  v.push_back(42);
}

在这种情况下,第一部分存储在堆栈中。那么#2如何被销毁呢?当向量的第一部分被销毁时,它也会销毁第二部分。这是通过在向量类的析构函数内删除底层数组来实现的。

2

2
如果您在STL容器类中存储指针,则需要在对象被销毁之前手动删除它们。这可以通过循环遍历整个容器并删除每个项目来完成,或者使用某种智能指针类。但是不要使用auto_ptr,因为它根本无法与容器一起使用。
好的副作用是,您可以在程序中保留多个指针容器,但只拥有其中一个容器拥有的对象,并且您只需要清理该容器即可。
删除指针的最简单方法是执行以下操作:
for (ContainerType::iterator it(container.begin()); it != container.end(); ++it)
{
    delete (*it);
}

这并不是一个好的建议(除非是析构函数的一部分),因为它不具备异常安全性。你应该使用一个能够理解自己持有指针的容器。 - Martin York
它为什么不是异常安全的?例如,如果ContainerType是类似于std::vectorstd::string*这样的东西,那么将此代码放入析构函数和“clear”或“reset”方法中是完全有效的。 - Dominik Grabiec

0
与堆中的任何其他对象一样,它必须手动销毁(使用delete)。


0
STL容器就像其他任何对象一样,如果你实例化了一个容器,它会被创建在栈上:
std::vector<int> vec(10);

就像任何其他堆栈变量一样,它只存在于定义它的函数范围内,并且不需要手动删除。STL容器的析构函数将调用容器中所有元素的析构函数。

在容器中保留指针是一个棘手的问题。由于指针没有析构函数,因此我会说您永远不想将裸指针放入STL容器中。以异常安全的方式执行此操作将非常困难,您必须在代码中散布try {} finally {}块,以确保所包含的指针始终被释放。

那么,除了裸指针之外,您应该把什么放入容器中?+1 jmucchiello提出boost::shared_ptr。boost::shared_ptr在STL容器中使用是安全的(不像std::auto_ptr)。它使用简单的引用计数机制,并且适用于不包含循环的数据结构。

如果数据结构包含循环,那么需要什么呢?在这种情况下,您可能希望升级到垃圾回收,这实际上意味着使用不同的语言,如Java。但这是另一个讨论。


0

回答你的第一个问题:

STL类没有什么特别之处(我希望如此)。它们的功能与其他模板类完全相同。因此,如果在堆上分配了它们,它们不会自动销毁,因为C++对它们没有垃圾回收(除非您使用一些复杂的autoptr技巧告诉它如何处理)。如果您在堆栈上分配它(而不是使用new),它很可能会被C++自动管理。

至于你的第二个问题,这里有一个非常简单的ArrayOfTen类来演示C++中典型内存管理的基础知识:

/* Holds ten Objects. */
class ArrayOfTen {
    public:
        ArrayOfTen() {
            m_data = new Object[10];
        }

        ~ArrayOfTen() {
            delete[] m_data;
        }

        Object &operator[](int index) {
            /* TODO Range checking */
            return m_data[index];
        }

    private:
        Object *m_data;

        ArrayOfTen &operator=(const ArrayOfTen &) { }
};

ArrayOfTen myArray;
myArray[0] = Object("hello world"); // bleh

基本上,ArrayOfTen类在堆上保留了十个对象元素的内部数组。当在构造函数中调用new[]时,在堆上分配了十个对象的空间,并构造了十个对象。同样,当在析构函数中调用delete[]时,十个对象被析构,然后先前分配的内存被释放。

对于大多数(所有?)STL类型,调整大小是在幕后完成的,以确保有足够的内存来容纳您的元素。上述类仅支持包含十个对象的数组。它基本上是Object[10]的非常限制性的typedef。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接