Есть ли в скобках после имени типа разница с новым?
Если "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
— это тип массива, каждый элемент инициализируется по умолчанию.- В противном случае инициализация не выполняется.