Avatar ·

Влияют ли скобки после имени типа в операторе new на инициализацию?

Всем привет! Я начал изучать C++ и столкнулся с непонятным моментом. Когда мы выделяем память через `new`, я часто вижу два варианта написания: ```cpp int* p1 = new int; // без скобок int* p2 = new int(); // со скобками ``` Или для классов: ```cpp MyClass* obj1 = new MyClass; MyClass* obj2 = new MyClass(); ``` Вроде бы разницы нет, но я где-то слышал, что для встроенных типов (int, double и т.д.) скобки могут влиять на инициализацию. Я проверил на простом примере: ```cpp #include int main() { int* a = new int; int* b = new int(); std::cout << *a << " " << *b; // Иногда выводит мусор и 0 delete a; delete b; return 0; } ``` Действительно, `*a` показывает странное значение, а `*b` всегда ноль. Почему так? И для классов это тоже имеет значение? Объясните, пожалуйста, как работает инициализация с `new` и без скобок. И что будет, если написать `new int(42)` — это же явно задаёт значение? Заранее спасибо! <<>>

Ключевое различие: инициализация по умолчанию vs. value-инициализация

Да, скобки после имени типа в new имеют принципиальное значение. Разница заключается в двух разных видах инициализации:

  • Без скобок (new int) — инициализация по умолчанию (default initialization).
  • Со скобками (new int()) — value-инициализация (value initialization).

Для встроенных типов (int, char, double и т.д.)

Это самый важный случай, который вы заметили:

int* p1 = new int;   // Значение *p1 НЕОПРЕДЕЛЕНО (мусор)
int* p2 = new int(); // Значение *p2 гарантированно равно 0

Почему так? Правила C++ гласят:

  • Инициализация по умолчанию для встроенных типов не выполняет никакой инициализации. В памяти остаётся то, что там было раньше (мусор).
  • Value-инициализация для встроенных типов обнуляет их (zero-initialization). Для int это 0, для bool — false, для double — 0.0.

Для классов с определённым конструктором по умолчанию

Если у класса есть конструктор по умолчанию (без параметров), то разницы нет — в обоих случаях будет вызван этот конструктор:

class MyClass {
public:
    MyClass() { std::cout << "Constructor called\n"; }
};

MyClass* obj1 = new MyClass;  // Вызовет конструктор
MyClass* obj2 = new MyClass();// Тоже вызовет конструктор

Оба варианта ведут себя одинаково.

Для классов без конструктора по умолчанию

Здесь разница может быть критичной. Если у класса нет конструктора по умолчанию:

class MyClass {
public:
    MyClass(int x) {} // Только конструктор с параметром
};

MyClass* obj1 = new MyClass;    // ОШИБКА компиляции!
MyClass* obj2 = new MyClass();  // ОШИБКА компиляции!

Оба варианта не скомпилируются, так как компилятор не может сгенерировать конструктор по умолчанию.

Для классов с тривиальным конструктором (POD-типы)

Если класс — это агрегат (например, struct без пользовательских конструкторов):

struct Point {
    int x;
    int y;
};

Point* p1 = new Point;   // x и y НЕ инициализированы (мусор)
Point* p2 = new Point(); // x и y обнулены (0, 0)

Здесь снова проявляется разница: без скобок — мусор, со скобками — обнуление всех полей.

Явная инициализация: new int(42)

Когда вы пишете new int(42) или new MyClass("hello") — это прямая инициализация (direct initialization). Скобки здесь используются для передачи аргументов конструктору. Это не то же самое, что пустые скобки. new int(42) создаст int со значением 42.

Рекомендация

Для встроенных типов и POD-структур всегда используйте new T() (с пустыми скобками), чтобы избежать неопределённого поведения из-за мусора в памяти. Для классов с конструктором по умолчанию разницы нет, но для единообразия многие разработчики пишут new Class().

Важно: В современном C++ (C++11 и новее) предпочтительнее использовать умные указатели (std::unique_ptr, std::make_unique) и контейнеры STL, чтобы избежать ручного управления памятью. std::make_unique<int>() всегда выполняет value-инициализацию (обнуление).

<<>>

Дополнение: списки инициализации в C++11

В C++11 появился ещё один синтаксис — uniform initialization (универсальная инициализация) с фигурными скобками:

int* p = new int{};     // Value-инициализация, *p = 0
int* q = new int{42};   // Прямая инициализация, *q = 42
Point* pt = new Point{10, 20}; // Агрегатная инициализация

Этот синтаксис работает одинаково для всех типов и часто считается более безопасным, так как он предотвращает most vexing parse (самую неприятную проблему синтаксического анализа C++).

Сравните:

MyClass obj(); // Ошибка! Это объявление функции, а не объекта!
MyClass obj{}; // OK, создание объекта с value-инициализацией

Поэтому в современном коде часто рекомендуют использовать фигурные скобки для инициализации. Однако для new это работает так же, как и круглые скобки: new int{} — это value-инициализация (обнуление), а new int — инициализация по умолчанию (мусор).

Итог: если вы хотите гарантированно обнулить память при выделении через new, используйте new T() или new T{}. Без скобок полагайтесь на то, что компилятор оставит мусор для встроенных типов.