时间复杂度删除运营商

什么是delete[]运算符的时间复杂性

我的意思是它是如何实现的 – 是否遍历数组中的所有元素并调用每个元素的析构函数?

这个操作符是否对原始typesint等)和用户定义的types做同样的事情?

::operator delete[]logging在cplusplus.com上 (有时会被忽视 ):

operator delete[]可以显式调用为常规函数,但在C ++中, delete[]是具有非常特定行为的运算符:具有delete[]运算符的expression式首先调用数组中每个元素的适当析构函数如果这些是类types的),然后调用函数operator delete[] (即这个函数)来释放存储。

所以析构函数被调用n次(每个元素一次),然后释放内存释放“函数”一次。

注意每个销毁可能需要一个不同的时间(甚至是复杂的)。 一般而言,大多数破坏是快速的,并具有相同的复杂性….但是,如果每个被破坏的元素是一个复杂的树或节点或graphics,情况就不会这样…

对于像int的基本types, int的虚构析构函数是没有操作的。 编译器可能会优化(如果问)。

你应该检查真正的C ++ 11标准,或者至less是n3337后期的工作草案,在n3337的§5.3.5.6页110中说(感谢Matteo Italia在评论中指出)

如果delete-expression的操作数的值不是空指针值,则delete-expression将调用该对象的析构函数(如果有)或要删除的数组的元素。 在数组的情况下,这些元素将按照地址递减的顺序被销毁(即按照构造函数完成的相反顺序;见12.6.2)

如果你使用了GCC 4.8以上的版本,那么你可以使用g++编译器和-fdump-tree-phiopt或者-fdump-tree-all选项(注意,它们正在倾倒大量的文件!),或者MELT插件来查询某个示例的中间Gimple表示。 或者使用-S -fverbose-asm来获取汇编代码。 而且您还想要添加优化标志,如-O1-O2

注意:恕我直言, cppreference.com也是一个有趣的网站关于C + +,看到有关delete (由Cubbi评论)

deletedelete[]的实现由两个阶段组成:

  1. recursion调用析构函数(如果有的话)
  2. 内存释放已删除的对象

更不要说一连串的调用析构函数了,它们的复杂性基本上由你来pipe理,我们留下的是如何释放内存来考虑。

第二点不在C ++规范中。 所以,任何编译器套件/操作系统都可以自由采用自己的策略。

一个常见的内存分配/释放策略是在需要的时候从操作系统分配一个完整的内存页面,然后在每个new / new[]返回一个合适大小的块,其长度和属性存储在页面内部作为头/页脚。 相应的delete / delete[]可以像将“chunk”标记为“free”一样简单,这显然是O(1)。

如果内存释放的复杂性是O(1),那么delete的复杂性实质上就是对析构函数的调用 。 默认实现(几乎)什么都不做,对于单个调用来说是O(1),因此总体上是O(n) ,其中n是调用的总体数量(例如,如果被破坏的对象有两个析构函数为称为,那么n = 1 (object) + 2 (o. fields) = 3 )。

把所有的东西放在一起:你可以通过在析构函数中执行操作来任意增加复杂性(可以由你写),但是你不能比在前一段中定义的O(n)更好地执行。 正式的说法是:“ delete的复杂性是欧米茄(n)”


¹请允许我在这一点上有点不正式

对于类types,理论上的复杂度是O(n) 。 为每个元素调用析构函数。 当然,要坚持可观察的行为,所以如果析构函数是无操作的,或者行为与将整个块标记为释放相同,则复杂度可能只是O(1)

对于原始types,编译器可能会立即释放整个内存块,因此复杂度为O(1)

什么是delete[]运算符的时间复杂性?

所需的时间量当然是实现定义的。 但是,操作符只适用于指向一维数组的指针,因此它是O(1)。

我的意思是它是如何实现的 – 是否遍历数组中的所有元素并调用每个元素的析构函数?

是。
只要它只在指定了使用new[]创build的内存的精确指针上被调用。 对于原始types,没有用户定义的析构函数。

这个操作符是否对原始types(int等)和用户定义的types做同样的事情?

比较不公平,因为原始types没有用户定义的析构函数,也不能是多态的。
例如,在多态类上delete[] 是一个未定义的行为 。 即

 Base* p1 = new Derived, *p2 = new Derived[2]; delete p1; // ok delete[] p2; // bad