Есть ли в скобках после имени типа разница с новым?

Если "Test" - обычный класс, есть ли разница между:

Test* test = new Test;

а также

Test* test = new Test();

8 ответов

Решение

Давайте станем педантичными, потому что есть различия, которые могут реально повлиять на поведение вашего кода. Многое из следующего взято из комментариев, сделанных к статье "Old New Thing".

Иногда память, возвращаемая оператором new, будет инициализирована, а иногда это не будет зависеть от того, является ли тип, который вы обновляете, POD (обычные старые данные) или это класс, который содержит члены POD и использует сгенерированный компилятором конструктор по умолчанию.

  • В C++1998 есть 2 типа инициализации: ноль и по умолчанию
  • В C++2003 был добавлен третий тип инициализации, инициализация значения.

Предполагать:

struct A { int m; }; // POD
struct B { ~B(); int m; }; // non-POD, compiler generated default ctor
struct C { C() : m() {}; ~C(); int m; }; // non-POD, default-initialising m

В компиляторе C++98 должно происходить следующее:

  • new A - неопределенное значение
  • new A() - инициализация нуля

  • new B - конструкция по умолчанию (B::m неинициализирован)

  • new B() - конструкция по умолчанию (B::m неинициализирован)

  • new C - конструкция по умолчанию (C::m инициализируется нулями)

  • new C() - конструкция по умолчанию (C::m инициализируется нулями)

В C++03-совместимом компиляторе все должно работать так:

  • new A - неопределенное значение
  • new A() - value-initialize A, которая является нулевой инициализацией, поскольку это POD.

  • new B - инициализация по умолчанию (оставляет B:: m неинициализированным)

  • new B() - value-initializes B, который инициализирует нулями все поля, так как его ctor по умолчанию генерируется компилятором, а не определяется пользователем.

  • new C - default-инициализирует C, который вызывает ctor по умолчанию.

  • new C() - значение инициализирует C, который вызывает ctor по умолчанию.

Так что во всех версиях C++ есть разница между new A а также new A() потому что А это ПОД.

И есть разница в поведении между C++98 и C++03 для случая new B(),

Это один из пыльных уголков C++, который может свести вас с ума. При создании объекта иногда вам нужны / нужны парены, иногда вы совершенно не можете их получить, а иногда это не имеет значения.

new Thing(); очевидно, что вы хотите конструктор под названием в то время как new Thing; подразумевается, что вы не против, если конструктор не вызывается.

Если используется в структуре / классе с определяемым пользователем конструктором, разницы нет. Если вызывается на тривиальной структуре / классе (например, struct Thing { int i; };) затем new Thing; как malloc(sizeof(Thing)); в то время как new Thing(); как calloc(sizeof(Thing)); Инициализируется ноль.

Гоча лежит между:

struct Thingy {
  ~Thingy(); // No-longer a trivial class
  virtual WaxOn();
  int i;
};

Поведение new Thingy; против new Thingy(); в этом случае изменилось между C++98 и C++2003. Смотрите объяснение Майкла Берра о том, как и почему.

В общем случае мы имеем инициализацию по умолчанию в первом случае и инициализацию значения во втором случае.

Например: в случае с int (тип POD):

  • int* test = new int - у нас есть любая инициализация и значение *test может быть любым.

  • int* test = new int() - * тест будет иметь значение 0.

Следующее поведение зависит от вашего типа Test. У нас есть разные случаи: Test имеет конструктор defult, Test сгенерировал конструктор по умолчанию, Test содержит член POD, не член POD...

Нет, они одинаковы. Но есть разница между:

Test t;      // create a Test called t

а также

Test t();   // declare a function called t which returns a Test

Это из-за основного правила C++ (и C): если что-то может быть объявлением, то это объявление.

Изменить: Относительно проблем инициализации, касающихся данных POD и не POD, хотя я согласен со всем, что было сказано, я просто хотел бы отметить, что эти проблемы применимы только в том случае, если вещь, которая была новой или сконструированной иным образом, не имеет определяемый пользователем конструктор. Если есть такой конструктор, он будет использован. Для 99,99% разумно разработанных классов будет такой конструктор, и поэтому проблемы можно игнорировать.

Предполагая, что Test является классом с определенным конструктором, нет никакой разницы. Последняя форма немного упрощает работу конструктора Test, но это все.

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

Если я говорю:

int my_int; // default-initialize → indeterminate (non-class type)

затем my_intимеет неопределенное значение, так как это неклассовый тип. В качестве альтернативы я могу инициализировать значениеmy_int (который для неклассовых типов инициализируется нулем) следующим образом:

int my_int{}; // value-initialize → zero-initialize (non-class type)

(Конечно, я не могу использовать () потому что это будет объявление функции, но int() работает так же, как int{} построить временный.)

А для типов классов:

Thing my_thing; // default-initialize → default ctor (class type)
Thing my_thing{}; // value-initialize → default-initialize → default ctor (class type)

Конструктор по умолчанию вызывается для создания Thing, без исключений.

Итак, правила более-менее:

  • Это тип класса?
    • ДА: вызывается конструктор по умолчанию, независимо от того, инициализирован ли он значением (с{}) или инициализированный по умолчанию (без {}). (Существует некоторое дополнительное предварительное обнуление с инициализацией значения, но последнее слово всегда остается за конструктором по умолчанию.)
    • НЕТ: Были{} используемый?
      • ДА: объект инициализируется значением, которое для неклассовых типов более или менее инициализируется нулем.
      • НЕТ: объект инициализируется по умолчанию, что для неклассовых типов оставляет ему неопределенное значение (фактически он не инициализируется).

Эти правила точно отражают new синтаксис с добавленным правилом, которое () можно заменить на {} так как newникогда не анализируется как объявление функции. Так:

int* my_new_int = new int; // default-initialize → indeterminate (non-class type)
Thing* my_new_thing = new Thing; // default-initialize → default ctor (class type)
int* my_new_zeroed_int = new int(); // value-initialize → zero-initialize (non-class type)
     my_new_zeroed_int = new int{}; // ditto
       my_new_thing = new Thing(); // value-initialize → default-initialize → default ctor (class type)

(Этот ответ включает в себя концептуальные изменения в C++11, которых в настоящее время нет в верхнем ответе; в частности, новый скаляр или экземпляр POD, который в конечном итоге будет иметь неопределенное значение, теперь технически инициализируется по умолчанию (что для типов POD технически вызывает тривиальный конструктор по умолчанию). Хотя это не приводит к значительным практическим изменениям в поведении, но несколько упрощает правила.)

Я написал несколько примеров кода ниже в качестве дополнения к ответу Майкла Берра:

      #include <iostream>

struct A1 {
    int i;
    int j;
};

struct B {
    int k;
    B() : k(4) {}
    B(int k_) : k(k_) {}
};

struct A2 {
    int i;
    int j;
    B b;
};

struct A3 {
    int i;
    int j;
    B b;
    A3() : i(1), j(2), b(5) {}
    A3(int i_, int j_, B b_): i(i_), j(j_), b(b_) {}
};

int main() {
    {
        std::cout << "Case#1: POD without ()\n";
        A1 a1 = {1, 2};
        std::cout << a1.i << " " << a1.j << std::endl;
        A1* a = new (&a1) A1;
        std::cout << a->i << " " << a->j  << std::endl;
    }
    {
        std::cout << "Case#2: POD with ()\n";
        A1 a1 = {1, 2};
        std::cout << a1.i << " " << a1.j << std::endl;
        A1* a = new (&a1) A1();
        std::cout << a->i << " " << a->j  << std::endl;
    }
    {
        std::cout << "Case#3: non-POD without ()\n";
        A2 a1 = {1, 2, {3}};
        std::cout << a1.i << " " << a1.j << " " << a1.b.k << std::endl;
        A2* a = new (&a1) A2;
        std::cout << a->i << " " << a->j << " " << a->b.k << std::endl;
    }
    {
        std::cout << "Case#4: non-POD with ()\n";
        A2 a1 = {1, 2, {3}};
        std::cout << a1.i << " " << a1.j << " " << a1.b.k  << std::endl;
        A2* a = new (&a1) A2();
        std::cout << a->i << " " << a->j << " " << a1.b.k << std::endl;
    }
    {
        std::cout << "Case#5: user-defined-ctor class without ()\n";
        A3 a1 = {11, 22, {33}};
        std::cout << a1.i << " " << a1.j << " " << a1.b.k << std::endl;
        A3* a = new (&a1) A3;
        std::cout << a->i << " " << a->j << " " << a->b.k << std::endl;
    }
    {
        std::cout << "Case#6: user-defined-ctor class with ()\n";
        A3 a1 = {11, 22, {33}};
        std::cout << a1.i << " " << a1.j << " " << a1.b.k  << std::endl;
        A3* a = new (&a1) A3();
        std::cout << a->i << " " << a->j << " " << a1.b.k << std::endl;
    }
    return 0;
}

/*
output with GCC11.1(C++20)
Case#1: POD without ()
1 2
1 2
Case#2: POD with ()
1 2
0 0
Case#3: non-POD without ()
1 2 3
1 2 4
Case#4: non-POD with ()
1 2 3
0 0 4
Case#5: user-defined-ctor class without ()
11 22 33
1 2 5
Case#6: user-defined-ctor class with ()
11 22 33
1 2 5
*/

Согласно n4713:

8.5.2.4/18:

Новое выражение, создающее объект типа, инициализирует этот объект следующим образом:

  • Если новый-инициализатор опущен, объект инициализируется по умолчанию (11.6).
  • В противном случае новый инициализатор интерпретируется в соответствии с правилами инициализации 11.6 для прямой инициализации.

11.6/11:

Объект, инициализатор которого представляет собой пустой набор круглых скобок, т. е. , должен быть инициализирован значением.

[Примечание: поскольку это запрещено синтаксисом инициализатора,

       X a();

это не объявление объекта класса, а объявление функции, не принимающей аргументов и возвращающейX. Форма разрешена в некоторых других контекстах инициализации (8.5.2.4, 8.5.1.3, 15.6.2). - последнее примечание]

Также в 11.6/(17.4):

  • Если инициализатор равен , объект инициализируется значением.

Итак, ответ заключается в том, что этот объект будет инициализирован по значению, а другой (без явного инициализатора) будет инициализировать этот объект по умолчанию.

11,6/8:

Инициализация значения объекта типа означает:

  • если это тип класса (возможно, с указанием cv) либо без конструктора по умолчанию, либо с конструктором по умолчанию, который предоставляется или удаляется пользователем, то объект инициализируется по умолчанию;
  • Если это тип класса (возможно, квалифицированный cv) без предоставленного пользователем или удаленного конструктора по умолчанию, то объект инициализируется нулем и проверяются семантические ограничения для инициализации по умолчанию, а если имеет нетривиальный конструктор по умолчанию, объект инициализируется по умолчанию;
  • Если это тип массива, каждый элемент инициализируется значением;
  • в противном случае объект инициализируется нулем.

11,6/7:

Инициализация объекта типа по умолчанию означает:

  • Если это тип класса (возможно, cv-квалифицированный), конструкторы рассматриваются. Перечислены применимые конструкторы и лучший из них для инициализатора.()выбирается посредством разрешения перегрузки. Выбранный таким образом конструктор вызывается с пустым списком аргументов для инициализации объекта.
  • ЕслиT— это тип массива, каждый элемент инициализируется по умолчанию.
  • В противном случае инициализация не выполняется.
Другие вопросы по тегам