Если T
не имеет тривиального деструктора, то для обычных реализаций компилятора new T[1]
имеет издержки по сравнению с new T
. Версия массива будет выделять немного большую область памяти для хранения количества элементов, поэтому на delete[]
он знает, сколько деструкторов должно быть вызвано.
Итак, у него есть издержки:
- должна быть выделена немного большая область памяти
delete[]
будет немного медленнее, поскольку для вызова деструкторов требуется цикл, вместо этого вызывается простой деструктор (здесьразница заключается в издержках цикла)
Проверьте эту программу:
#include <cstddef>
#include <iostream>
enum Tag { tag };
char buffer[128];
void *operator new(size_t size, Tag) {
std::cout<<"single: "<<size<<"\n";
return buffer;
}
void *operator new[](size_t size, Tag) {
std::cout<<"array: "<<size<<"\n";
return buffer;
}
struct A {
int value;
};
struct B {
int value;
~B() {}
};
int main() {
new(tag) A;
new(tag) A[1];
new(tag) B;
new(tag) B[1];
}
На моей машине она печатает:
single: 4
array: 4
single: 4
array: 12
Потому что B
имеет нетривиальный деструктор, компилятор выделяет дополнительные 8 байтов для хранения количества элементов (поскольку это 64-битная компиляция, для этого требуется 8 дополнительных байтов) для версии массива. Поскольку A
выполняет тривиальный деструктор, версия массива A
не нуждается в этом дополнительном пространстве.
Примечание: как отмечает Deduplicator, есть небольшое преимущество в производительности при использовании версии массива,если деструктор является виртуальным: в delete[]
компилятору не нужно вызывать деструктор виртуально, потому что он знает, что типом является T
. Вот простой пример, чтобы продемонстрировать это:
struct Foo {
virtual ~Foo() { }
};
void fn_single(Foo *f) {
delete f;
}
void fn_array(Foo *f) {
delete[] f;
}
Clang оптимизирует этот случай, но GCC не делает: godbolt .
Для fn_single
clang испускает проверку nullptr
, а затем виртуально вызывает функцию destructor+operator delete
. Это должно быть сделано так, как f
может указывать на производный тип, у которого есть непустой деструктор.
Для fn_array
, clang испускает проверку nullptr
, а затем вызывает operator delete
, без вызова деструктора, так как он пуст. Здесь компилятор знает, что f
фактически указывает на массив Foo
объектов, он не может быть производным типом, поэтому он может опускать вызовы пустых деструкторов.