Начнем с new T()
.
Стандарт говорит нам, что если Т
является POD-классом (не объектно-ориентированной сущностью), то объект будет инициализирован значением по умолчанию (обычно, например, для арифметических типов это 0), а если это не POD-класс (явная объектно-ориентированная сущность), то для него вызовется конструктор по умолчанию (либо явный, либо созданный компилятором). Если конструктор по умолчанию задан явно, то будет вызван только он, и вся ответственность за инициализацию ляжет на него. Никой инициализации по умолчанию больше не будет. Если же конструктор по умолчанию не задан явно, и компилятор создал его сам, и в этом случае все члены класса будут проинициализированы неявно: POD-объекты будут проинициализированы нулем, а для не-POD объектов будет проведена инициализация по умолчанию (включая всех его дочерних составляющих — рекурсивный обход всех подобъектов и их инициализация по такому же принципу).
Теперь new T
.
В этом случае для POD-объектов вообще не будет никакой инициализации (что было в памяти на момент распределения, то и будет). Для не POD-объекта просто будет вызван конструктор по умолчанию (либо явный, ли заданный компилятором по умолчанию), и не будет проводиться никакой инициализации POD-составляющих этого объекта.
Для простоты, POD-типами (Plain Old Data) является все наследие языка С в С++. Везде, где есть объектно-ориентированная примесь — это уже не POD-класс. Для не POD-классов нельзя делать никаких предположений о внутренней структуре, расположению в памяти и т.д.
Забавно, структура:
struct A { int b; };
является POD-типом, а вот если добавить в нее, например, слово public
:
struct A { public: int b; };
то по стандарту это не POD-объект, и его нельзя уже трогать на уровне внутреннего представления, например обнулить через memset
. Хотя многие компиляторы разрешают такие “игры” с не POD-объектами и, программа может в принципе работать, но это против стандарта, и, конечно, против переносимости программы.
Итак, описание различий весьма путанное, поэтому лучше рассмотреть пример.
Для чистоты эксперимента я буду использовать так называемое распределение памяти с размещением. То есть я вручную указываю, в каком месте памяти должен будет создаваться объект. Это позволит контролировать “непредсказуемые” значения неинициализированной памяти.
Итак, первый пример:
#include <iostream> #include <cstdlib> class T { public: // Для простоты экспериментируем на однобайтовом типе. unsigned char n; }; int main() { // "Случайная" память для создания объекта. // Берем с запасом, чтобы уж точно вместить объект класса T. char p[10240]; // Заполняем память числом 170 (0xAA) std::memset(p, 170 /* 0xAA */, sizeof(p)); // Создаем объект явно в памяти, заполненной числом 170. T* a = new (p) T; std::cout << "new T: T.n = " << (int)a->n << std::endl; // Заполняем память числом 171 (0xAB) std::memset(p, 171 /* 0xAB */, sizeof(p)); // Создаем объект явно в памяти, заполненной числом 171. T* b = new (p) T(); std::cout << "new T(): T.n = " << (int)b->n << std::endl; return 0; }
Данный пример выведет:
new T: T.n = 170
new T(): T.n = 0
Видно, что для new T
элемент T.n
так остался неинициализированным и равным числу 170
, которые заполнили память заранее. Для new T()
же в свою очередь элемент T.n
стал равны нулю, то есть он был проинициализирован. Все, как сказано в стандарте.
Теперь изменим одну маленькую деталь — добавим в класс Т
явный конструктор:
class T { public: // Явный конструктор. T() {} // Для простоты экспериментируем на однобайтовом типе. unsigned char n; };
Теперь нас ждет сюрприз. Теперь программа будет выводить следующее:
new T: T.n = 170
new T(): T.n = 171
Получается, что даже при new T()
элемент T.n
не был более инициализирован. Почему? А потому, что стандарт гласит: если задан явный конструктор класса, то никакие инициализации по умолчанию для POD-объектов не производятся. Раз программист задал конструктор явно, значит он знает что делает, и вся ответственность за инициализацию теперь на его плечах.
Лично для себя я всегда предпочитаю писать new T()
хотя бы для единообразия вызова конструкторов. Также я всегда явно инициализирую все POD-объекты вручную в конструкторе или в его списке инициализации. Отсутствие предположений о значении POD-типов по умолчанию и инициализация их принудительно позволяет избежать сюрпризов при смене компилятора.
Другие посты по теме: