Скобки после имени типа в new: есть ли разница?
Разница между new T и new T() в C++
Короткий ответ: да, разница есть, и она критична для встроенных типов и POD-структур. Для классов с пользовательским конструктором — обычно нет разницы.
Для встроенных типов (int, double, char и т.д.)
Здесь разница проявляется наиболее явно:
int* p1 = new int; // НЕ инициализируется — содержит мусор
int* p2 = new int(); // Инициализируется нулём → *p2 == 0
int* p3 = new int(42); // Инициализируется значением 42
То же самое для массивов:
int* arr1 = new int[10]; // 10 неинициализированных int (мусор)
int* arr2 = new int[10](); // 10 int, инициализированных нулём
Это поведение описано в стандарте C++ как value-initialization (для ()) vs default-initialization (без скобок). Для встроенных типов default-initialization оставляет значение неопределённым.
Для пользовательских классов
Если у класса есть конструктор по умолчанию:
class MyClass {
public:
int x;
MyClass() : x(100) {} // пользовательский конструктор
};
MyClass* obj1 = new MyClass; // Вызов конструктора по умолчанию
MyClass* obj2 = new MyClass(); // Тоже вызов конструктора по умолчанию
В этом случае разницы нет — оба варианта вызывают один и тот же конструктор. Скобки просто необязательны.
Для POD-типов и агрегатов
Если класс — POD (Plain Old Data) или агрегат без пользовательского конструктора:
struct Point {
int x;
int y;
};
Point* p1 = new Point; // x и y не инициализированы (мусор)
Point* p2 = new Point(); // x и y инициализированы нулём (0, 0)
Это та же логика, что и для встроенных типов: () включает value-initialization, которая обнуляет все поля.
Важное исключение: списки инициализации (C++11 и новее)
Начиная с C++11, появилась uniform initialization с фигурными скобками:
int* p = new int{}; // value-initialization → 0
Point* pt = new Point{}; // value-initialization → {0, 0}
int* arr = new int[5]{}; // все элементы — 0
Фигурные скобки {} ведут себя так же, как () для value-initialization, но есть нюанс: если у класса есть конструктор, принимающий std::initializer_list, то {} может вызвать его, а () — нет.
Когда может быть опасность?
Самая частая ошибка новичков — использование new int и чтение мусора:
int* ptr = new int;
std::cout << *ptr; // НЕОПРЕДЕЛЁННОЕ ПОВЕДЕНИЕ! Может вывести 0, а может -123456
Аналогично для массивов структур:
struct Data { int id; float value; };
Data* arr = new Data[100];
// arr[0].id — мусор! Используйте new Data[100]() или new Data[100]{}
Рекомендация
- Для встроенных типов и POD-структур всегда используйте
()или{}, чтобы гарантировать инициализацию нулём. - Для классов с конструктором — скобки не обязательны, но их можно ставить для единообразия.
- В современном C++ (C++11+) предпочитайте
auto ptr = std::make_unique<T>()илиstd::make_shared<T>()— они всегда выполняют value-initialization. - Избегайте сырого
newв пользу умных указателей — это снижает риск утечек и неопределённого поведения.
Таким образом, скобки после типа в new — это не просто синтаксический сахар, а способ явно указать компилятору: «инициализируй значением по умолчанию». Для встроенных типов это превращает мусор в ноль, что часто спасает от трудноотлавливаемых багов.
Альтернативный взгляд: когда разница неожиданна
Есть ещё один тонкий момент, связанный с агрегатной инициализацией в C++11 и новее. Рассмотрим такой код:
struct Widget {
int a;
int b = 5; // in-class initializer (C++11)
};
Widget* w1 = new Widget; // a — не инициализирован, b = 5
Widget* w2 = new Widget(); // a = 0, b = 5
Widget* w3 = new Widget{}; // a = 0, b = 5
Обратите внимание: new Widget оставляет a неинициализированным, хотя у b есть значение по умолчанию. Это может сбивать с толку. Если ваш класс — агрегат (все поля public, нет пользовательских конструкторов), то new Widget выполняет агрегатную инициализацию только если вы передаёте значения в скобках. Без скобок — это просто выделение памяти без инициализации членов, у которых нет in-class initializer.
Ещё один нюанс: для классов с удалённым конструктором по умолчанию:
struct NoDefault {
NoDefault() = delete;
int x;
};
NoDefault* p = new NoDefault; // Ошибка компиляции
NoDefault* p = new NoDefault(); // Тоже ошибка — конструктор удалён
NoDefault* p = new NoDefault{}; // Тоже ошибка
Но если добавить явный конструктор:
struct WithDefault {
WithDefault(int v = 0) : x(v) {}
int x;
};
WithDefault* p = new WithDefault; // OK, x = 0
WithDefault* p = new WithDefault(); // OK, x = 0
Мораль: всегда явно инициализируйте. Используйте () или {} для value-initialization, если не уверены. В современном C++ лучше вообще избегать сырого new — используйте std::make_unique и std::make_shared, которые гарантируют правильную инициализацию.