【问题标题】:vector::clear() behind the scenes & destructionvector::clear() 幕后和破坏
【发布时间】:2012-12-14 17:46:09
【问题描述】:

我研究了STL向量的实现。 矢量容器被实现为动态数组。 clear() 方法用于销毁向量中的所有元素,它将向量的大小设置为 0,但容量保持不变。所以,如果我理解正确,所有元素都被称为它们的析构函数,但动态分配的内存仍然可用。为了仍然释放它,我们可以这样做:

Vec.swap( vector<T>() ); // Capacity = 0.

但是让我们假设我们没有使用交换,只是明确了。 内部实现(如果我错了,请纠正我)大约等于以下内容(以非常简化的方式):

// A contained type:
struct C {
    int m;
    C() : m(123){}
};
C * arr = new C[10];  //  Suppose this is the internal array in the container

编辑: 我知道上面的 new 运算符在实际实现中并没有使用,而 STL 使用分配器,我只是使用 new 作为测试用例来测试析构函数(这只是一个类比)。

// Calling clear() :
for(size_t i=0;i<SZ;i++)
    arr[i].~C();  //  Destroying ALL elements  
// some other actions . . .

但是现在容量还是10,内存还有一些我们可以访问的数据:

// Accessing the vector at 0:
cout<<arr[0].m<<endl;  //  This prints 123

这是未定义的行为吗?嗯,似乎是这样,但我想确定一下。

也许如果我更深入地了解调用析构函数(关于堆栈内存)时会发生什么,我可以肯定地知道,这是否等于程序超出函数范围时调用析构函数,或者在退出作用域之前调用析构函数被认为和任何方法一样,并且对象的堆栈内存没有被释放?

免责声明:上面的代码非常简化,以象征 clear() 的部分功能,以及我从研究中得出的结论,如果我错了,您可以纠正我。

【问题讨论】:

  • 这是关于vector 的一些假设实现还是关于对象破坏的机制?
  • @honk - 实际上,由于 clear 调用破坏,它们都是相关的
  • 如果我可以给您一点建议,请阅读 Scott Meyer 的书籍:有效的 STL 和更有效的 C++。前者解释了你的疑问,公平的。

标签: c++ oop stl destructor undefined-behavior


【解决方案1】:

std::vector&lt;...&gt;::clear() 只是销毁对象并适当设置其内部记录以指示没有对象是正确的。当访问被破坏的对象时,您有未定义的行为:虽然数据中的位可能没有改变,但关联的对象也可能已被破坏,并且它们的内存被回收用于其他目的。

在您的示例中,C 仅存储 int 并且在析构函数中不对其执行任何操作,这些位可能没有变化,但不能保证这种方式。特别是调试实现可能会浪费几个周期来故意将垃圾写入被破坏对象的内存中。

附注:std::vector&lt;...&gt; 不会使用new C[n],而是通过分配器分配和释放原始内存。不过,这是一个细节。

【讨论】:

  • 在范围内调用析构函数与退出时的析构作用相同吗?对象的栈内存是否被释放?
  • @StackHeapCollision:显式销毁调用析构函数,自动销毁也是如此。如果您显式销毁分配在堆栈上的对象,您可能已经有未定义的行为。至少您需要在相同的内存位置构造一个对象,因为当执行离开函数时,所有堆栈分配的对象都会自动销毁。堆栈内存将用于堆栈:它不会使用任何正常的内存分配例程“释放”,尽管 - 毕竟,它也没有“分配”。系统会以某种方式管理堆栈内存。
  • 谢谢。通过“释放”从堆栈中排除,因为任何局部变量都从堆栈中插入/删除。当然,我并不是指动态分配术语中的“免费”。
【解决方案2】:

如果我理解正确,您的主要问题是:

这是未定义的行为吗?

使用常规数组,是的,您有一个未定义的行为,原因在前面的答案中解释过。但是,如果您尝试使用索引等于或大于向量的 size(注意,不要超出 容量)来访问 std::vector 的元素,那么访问运算符将抛出一个std::out_of_range 异常(尽管函数clear() 确实调用了向量的每个元素的析构函数并且没有改变它的容量,它确实改变了多少的内部计数向量包含的元素)。

【讨论】:

  • 是否保证抛出异常取决于std::vector&lt;...&gt;中的元素是如何被访问的:当使用v.at(i)和超出范围的索引i时会抛出异常。在问题中使用v[i] 时,结果是未定义的行为。它可能会抛出异常,但也可能导致鼻守护进程。
  • 您是对的,正如本页末尾的注释所证明的那样:cplusplus.com/reference/vector/vector/operator[] 但是,我找不到标准中提到这一点的地方。你碰巧知道吗?还是谢谢你
  • 根据23.2.3中的表101 [sequence.reqmts] c[i]相当于*(c.begin() + i)。根据 24.2.2 [iterator.iterators] 中的表 106,迭代器必须是可引用的,*it 才有效。根据 23.2.3 [sequence.reqmts] clear() 中的表 100,使所有指向容器元素的迭代器无效。我不认为有任何一个位置表明行为未定义,但将这些子句放在一起表明它是未定义的行为。
  • 对。实际上,表 101 为 at() 定义了相同的语义,但随后表 101 下方的第 17 条明确提到 at() 抛出了一个 out_of_range 异常,而它没有说明 []。非常感谢你
【解决方案3】:

您的程序使用内存区域:堆栈和堆。您将 arr 向量地址放在堆内存上,但 m 的值驻留在堆栈上。 如果要删除堆内存使用 delete [] arr,如果要删除未寻址的内存使用

arr=NULL 您的结构可以像这样轻松修改

struct C {
int *m;
C(int value) {
    m=new int(value);
}
~C()
{
    delete m;
}

};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-02
    • 1970-01-01
    • 1970-01-01
    • 2010-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-04
    相关资源
    最近更新 更多