I already know that the new[]
operator first allocates memory and then calls the constructor for each element and that the delete[]
operator first calls the destructor for each element and then frees memory and, because of that, they both have an O(n) time complexity.
But if I have a class, for which I have not defined any constructor/destructor, will the complexity still be O(n), or will it be just O(1)?
For instance, if I have two classes:
class foo
{
public:
int a;
foo()
{
a = 0;
// more stuff
}
~foo()
{
a = 1;
// some useful stuff here
}
};
class boo
{
public:
int a;
};
And I create two arrays of them like this:
int n = 1000;
foo* pfoo = new foo[n];
boo* pboo = new boo[n];
I'm pretty sure the first new
call will have an O(n) complexity, but what about the second? Will new
just allocate the necessary memory and that's it, or will it call some default constructor (I'm not sure if such thing actually exits in C++) for each element?
And the same question for delete
:
delete [] pfoo;
delete [] pboo;
When I delete the second array will the complexity still be O(n), or will delete
just deallocate the memory in O(1) complexity?
delete is used for one single pointer and delete[] is used for deleting an array through a pointer.
The formally correct way to state this is: "the complexity of delete is an Omega(n)".
Delete is an operator that is used to destroy array and non-array(pointer) objects which are created by new expression. Delete can be used by either using Delete operator or Delete [ ] operator. New operator is used for dynamic memory allocation which puts variables on heap memory.
Deleting the array itself would be O(1) because it would simply release the memory to the pool while deleting every item in the array would be O(n) because each item needs to be removed individually.
When you don't know, it's great idea to use assembly output. For example, let's assume this is the code to compare.
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;
}
When compiling with optimizations using gcc (Clang gives similar output), you get the following result.
.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
It's easy to tell that for foo
it calls destructor, but for boo
it directly calls delete []
function (_ZdaPv
after name mangling). This also happens without optimizations. The code is longer, because methods are actually output, but it's still noticeable that delete []
is called directly for 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
This also applies to new []
. _Znam
is called directly, without constructing objects, even without optimizations.
Generally, that means custom constructors or destructors mean that new []
and delete []
won't be executed in constant time. But if there aren't, the compiler doesn't try to call constructors or destructors for these, and they will be POD data types, which means that constructing these objects is simple malloc-like call. There are some exceptions (involving various optimizations), but usually code with constructors/destructors will be O(N), and without will be O(1), assuming O(1) new []
/delete []
implementation.
It depends on your exact syntax:
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;
gives the output (on my machine):
3452816845
3452816845
0
0
Because one will be default initialized and the other value initialized.
delete[]
on the other hand is even simpler to understand: If your data type has a destructor, it will be called. The built in (and thus POD) types generally do not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With