C++ 新建[],删除[]复杂性

C++ 新建[],删除[]复杂性,c++,memory-management,time-complexity,C++,Memory Management,Time Complexity,我已经知道,new[]操作符首先分配内存,然后为每个元素调用构造函数,delete[]操作符首先为每个元素调用析构函数,然后释放内存,因此,它们的时间复杂度都是O(n) 但是如果我有一个类,我没有为它定义任何构造函数/析构函数,那么它的复杂性仍然是O(n),还是仅仅是O(1) 例如,如果我有两个类: class foo { public: int a; foo() { a = 0; // more stuff } ~foo(

我已经知道,
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 [] pfoo;
delete [] pboo;
当我删除第二个数组时,复杂度仍然是O(n),还是
delete
只是在O(1)复杂度中释放内存

但是如果我有一个类,我没有为它定义任何构造函数/析构函数,那么它的复杂性仍然是O(n),还是仅仅是O(1)


成员本身可能仍然有析构函数。简而言之,对于POD,
delete[]
将是O(1)。

这取决于您的确切语法:

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;
因为一个将被默认初始化,另一个值将被初始化


另一方面,
delete[]
更容易理解:如果您的数据类型具有析构函数,则会调用它。内置(因此POD)类型通常不知道。当您不知道时,最好使用汇编输出。例如,假设这是要比较的代码

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[]
函数(
\u 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[]
<代码>\u Znam直接调用,无需构建对象,甚至无需优化

通常,这意味着自定义构造函数或析构函数意味着
new[]
delete[]
不会在固定时间内执行。但如果没有,编译器不会尝试调用这些对象的构造函数或析构函数,它们将是POD数据类型,这意味着构造这些对象是简单的malloc调用。也有一些例外(涉及各种优化),但通常带有构造函数/析构函数的代码将是O(N),没有构造函数的代码将是O(1),假设O(1)
new[]
/
delete[]
实现。

MyClass*p=static_cast(::操作符new(sizeof(MyClass[N]);
MyClass *p = static_cast<MyClass*> (::operator new (sizeof(MyClass[N])));

为N个对象分配内存,但不构造它们。这样,复杂性将与malloc()相同。它显然比分配和构造复杂类的对象更快。

我自己也试着检查汇编代码,但这就像是一种外来语言。我使用VisualC++,但是我认为它的编译器在这种情况下的行为与GCC几乎相同。非常感谢您花时间记录您的答案。
MyClass *p = static_cast<MyClass*> (::operator new (sizeof(MyClass[N])));