新增[],删除[]复杂性
new [], delete [] complexity
我已经知道new[]
运算符首先为每个元素分配内存,然后调用构造函数,delete[]
运算符首先为每一个元素调用析构函数,然后释放内存,因此,它们都具有O(n)时间复杂性。
但是,如果我有一个类,我没有为它定义任何构造函数/析构函数,那么它的复杂性仍然是O(n),还是只是O(1)?
例如,如果我有两个类:
class foo
{
public:
int a;
foo()
{
a = 0;
// more stuff
}
~foo()
{
a = 1;
// some useful stuff here
}
};
class boo
{
public:
int a;
};
我创建了两个这样的数组:
int n = 1000;
foo* pfoo = new foo[n];
boo* pboo = new boo[n];
我很确定第一个new
调用的复杂性为O(n),但第二个呢?new
会只分配必要的内存吗?还是会为每个元素调用一些默认的构造函数(我不确定C++中是否真的存在这样的东西)?
和delete
:的相同问题
delete [] pfoo;
delete [] pboo;
当我删除第二个数组时,复杂性仍然是O(n),还是delete
只是以O(1)复杂性释放内存?
如果您不知道,最好使用汇编输出。例如,让我们假设这是要进行比较的代码。
class foo
{
public:
int a;
foo()
{
a = 0;
// more stuff
}
~foo()
{
a = 1;
// some useful stuff here
}
};
class boo
{
public:
int a;
};
void remove_foo(foo* pfoo) {
delete [] pfoo;
}
void remove_boo(boo *pboo) {
delete [] pboo;
}
当使用gcc进行优化编译时(Clang会给出类似的输出),您会得到以下结果。
.file "deleter.cpp"
.text
.p2align 4,,15
.globl _Z10remove_fooP3foo
.type _Z10remove_fooP3foo, @function
_Z10remove_fooP3foo:
.LFB6:
.cfi_startproc
testq %rdi, %rdi
je .L1
movq -8(%rdi), %rax
leaq (%rdi,%rax,4), %rax
cmpq %rax, %rdi
je .L4
.p2align 4,,10
.p2align 3
.L6:
subq $4, %rax
movl $1, (%rax)
cmpq %rax, %rdi
jne .L6
.L4:
subq $8, %rdi
jmp _ZdaPv
.p2align 4,,10
.p2align 3
.L1:
rep ret
.cfi_endproc
.LFE6:
.size _Z10remove_fooP3foo, .-_Z10remove_fooP3foo
.p2align 4,,15
.globl _Z10remove_booP3boo
.type _Z10remove_booP3boo, @function
_Z10remove_booP3boo:
.LFB7:
.cfi_startproc
testq %rdi, %rdi
je .L8
jmp _ZdaPv
.p2align 4,,10
.p2align 3
.L8:
rep ret
.cfi_endproc
.LFE7:
.size _Z10remove_booP3boo, .-_Z10remove_booP3boo
.ident "GCC: (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]"
.section .note.GNU-stack,"",@progbits
很容易看出,对于foo
,它调用析构函数,但对于boo
,它直接调用delete []
函数(名称篡改后的_ZdaPv
)。在没有优化的情况下也会发生这种情况。代码更长,因为方法实际上是输出的,但仍然值得注意的是,delete []
是直接为boo
调用的。
.file "deleter.cpp"
.section .text._ZN3fooD2Ev,"axG",@progbits,_ZN3fooD5Ev,comdat
.align 2
.weak _ZN3fooD2Ev
.type _ZN3fooD2Ev, @function
_ZN3fooD2Ev:
.LFB4:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movl $1, (%rax)
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE4:
.size _ZN3fooD2Ev, .-_ZN3fooD2Ev
.weak _ZN3fooD1Ev
.set _ZN3fooD1Ev,_ZN3fooD2Ev
.text
.globl _Z10remove_fooP3foo
.type _Z10remove_fooP3foo, @function
_Z10remove_fooP3foo:
.LFB6:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
pushq %rbx
subq $24, %rsp
.cfi_offset 3, -24
movq %rdi, -24(%rbp)
cmpq $0, -24(%rbp)
je .L3
movq -24(%rbp), %rax
subq $8, %rax
movq (%rax), %rax
leaq 0(,%rax,4), %rdx
movq -24(%rbp), %rax
leaq (%rdx,%rax), %rbx
.L6:
cmpq -24(%rbp), %rbx
je .L5
subq $4, %rbx
movq %rbx, %rdi
call _ZN3fooD1Ev
jmp .L6
.L5:
movq -24(%rbp), %rax
subq $8, %rax
movq %rax, %rdi
call _ZdaPv
.L3:
addq $24, %rsp
popq %rbx
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE6:
.size _Z10remove_fooP3foo, .-_Z10remove_fooP3foo
.globl _Z10remove_booP3boo
.type _Z10remove_booP3boo, @function
_Z10remove_booP3boo:
.LFB7:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movq %rdi, -8(%rbp)
cmpq $0, -8(%rbp)
je .L7
movq -8(%rbp), %rax
movq %rax, %rdi
call _ZdaPv
.L7:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE7:
.size _Z10remove_booP3boo, .-_Z10remove_booP3boo
.ident "GCC: (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]"
.section .note.GNU-stack,"",@progbits
这也适用于new []
。直接调用_Znam
,无需构造对象,甚至无需优化。
通常,这意味着自定义构造函数或析构函数意味着new []
和delete []
不会在恒定时间内执行。但如果没有,编译器就不会尝试为这些对象调用构造函数或析构函数,它们将是POD数据类型,这意味着构造这些对象是类似malloc的简单调用。有一些例外(涉及各种优化),但通常具有构造函数/析构函数的代码将为O(N),而不具有构造函数/析构函数的则为O(1),假设实现为O(2)new []
/delete []
。
这取决于您的确切语法:
auto x = new unsigned[2];
auto y = new unsigned[2]();
::std::cout << x[0] << "n" << x[1] << "n" << y[0] << "n" << y[1] << "n";
delete[] x;
delete[] y;
给出输出(在我的机器上):
3452816845
3452816845
0
0
因为一个值将被默认初始化,另一个值则被初始化。
另一方面,delete[]
更容易理解:如果你的数据类型有一个析构函数,它就会被调用。内置(以及POD)类型通常不会。
MyClass *p = static_cast<MyClass*> (::operator new (sizeof(MyClass[N])));
为N个对象分配内存,而不构造它们。这样一来,复杂性将与malloc()相同。它显然会比分配和构造复杂类的对象更快。
但是,如果我有一个类,我没有为它定义任何构造函数/析构函数,那么它的复杂性仍然是O(n),还是只是O(1)?
成员本身可能仍然具有析构函数。简而言之,对于POD,delete[]
将是O(1)。
- 将数组的地址分配给变量并删除
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- C/C++编译器通常会删除重复的库吗
- 从链接列表c++中删除一个项目
- C++如何通过用户输入删除列表元素
- 为什么在C++中使用私有复制构造函数与删除复制构造函数
- 是否需要删除包含对象的"pair"?
- 如何在自删除后将对象设置为nullptr
- 迭代时从向量和内存中删除对象
- 使用函数"remove"删除重复元素
- 如何从多映射中删除特定的重复项
- 运算符C++ "delete []"仅删除 2 个前值
- 删除指向指针的指针是运行时错误吗
- 将指针设置为"nullptr"并不能防止双重删除?
- 为什么示例代码访问IUnknown中已删除的内存
- 了解从链接列表中删除重复项的复杂性
- 二进制树搜索删除节点复杂性
- 哈希表删除复杂性
- 从高效方法中删除指定的字符(时间和空间复杂性)
- 新增[],删除[]复杂性