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-типов по умолчанию и инициализация их принудительно позволяет избежать сюрпризов при смене компилятора.
Другие посты по теме:
> Лично для себя я всегда предпочитаю писать new T() хотя бы для единообразия вызова конструкторов
ОтветитьУдалитьОдна проблема:
T x();
это не локальный объект x типа T, а прототип функции.
cd-riper: Согласен. При вызове конструктора без использования new есть неоднозначность в грамматике, когда выражение может выглядеть как и вызов конструктора и как декларация функции, и по стандарту в этом случае компилятор рассматривает это как декларацию функции.
ОтветитьУдалитьЯ немного покопался в этой теме недавно - http://easy-coding.blogspot.com/2009/02/c.html.
Благо Мейерс и Саттер все детально разжевали этот кислый вопрос.
Уточню свой подход: для "new" я обычно пишу "new T()", ну а для автоматического объявления приходится писать просто "T x".
Александр, Вы пишете:
ОтветитьУдалить>Если же конструктор по умолчанию не задан явно, и компилятор создал его сам, и в этом случае все члены класса будут проинициализированы неявно: POD-объекты будут проинициализированы нулем, а для не-POD объектов будет проведена инициализация по умолчанию (включая всех его дочерних составляющих — рекурсивный обход всех подобъектов и их инициализация по такому же принципу).
Это полностью соответствует стандарту (5.3.4/15), но его не выполняет ни один из компиляторов, на которых я тестировал эту проблему, а именно VS разных версий и gcc(g++). Для того, чтобы убедиться в этом, достаточно добавить не-POD поле в структуру T. В обоих случаях в поле n будет мусор. То есть для не-POD данных new T() аналогично new T. По поводу VS есть официальный баг: http://connect.microsoft.com/VisualStudio/feedback/details/100744/value-initialization-in-new-expression , но он "Closed as Won't Fix". Так что, видимо, правильнее будет придерживаться Вашего совета:
>Также я всегда явно инициализирую все POD-объекты вручную в конструкторе или в его списке инициализации.