Новая [], удалить [] сложность

Я уже знаю, что оператор 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 просто выделить нужную память и что она или вызовет какой-то конструктор по умолчанию (я не уверен, что такая вещь действительно выходит на С++) для каждого элемента?

И тот же вопрос для delete:

delete [] pfoo;
delete [] pboo;

Когда я удаляю второй массив, все еще будет сложность O (n) или будет delete просто освободить память в O (1) сложности?

Ответы

Ответ 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 (1) new []/delete [].

Ответ 2

Это зависит от вашего точного синтаксиса:

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) типы обычно не работают.

Ответ 3

Но если у меня есть класс, для которого я не определил никакого конструктора/деструктора, будет ли еще сложна сложность O (n), или это будет просто O (1)?

У самих членов могут быть деструкторы. Короче говоря, для POD delete[] будет O (1).

Ответ 4

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

Выделяет память для N объектов и не создает их. Таким образом, сложность будет такой же, как malloc(). Это будет, очевидно, быстрее, а затем выделение и построение объектов сложного класса.