如果“Test”是一个普通类,下面两种写法有什么区别:
Test* test = new Test;
并且
Test* test = new Test();
如果“Test”是一个普通类,下面两种写法有什么区别:
Test* test = new Test;
并且
Test* test = new Test();
让我们变得迂腐一些,因为有些差异实际上会影响到你的代码行为。以下大部分内容摘自《旧日新事》(Old New Thing)文章的评论。
有时,new运算符返回的内存将被初始化,有时不会,这取决于你要new的类型是POD(纯旧数据)还是包含POD成员并使用编译器生成的默认构造函数的类。
假设:
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
new A
- 不确定的值
new A()
- 零初始化
new B
- 默认构造(B::m未初始化)
new B()
- 默认构造(B::m未初始化)
new C
- 默认构造(C::m零初始化)
new C()
- 默认构造(C::m零初始化)
new A
- 不确定的值
new A()
- 对A进行值初始化,因为它是一个POD类型,所以会进行零初始化。
new B
- 默认初始化(B::m保持未初始化状态)
new B()
- 对B进行值初始化,因为其默认构造函数是由编译器生成的而不是用户定义的,所以会对所有字段进行零初始化。
new C
- 默认初始化C,调用默认构造函数。
new C()
- 对C进行值初始化,调用默认构造函数。
因此,在所有版本的C++中,new A
和new A()
之间存在差异,因为A是一个POD类型。
而对于new B()
这种情况,在C++98和C++03之间存在行为上的差异。
new A()
将像new B()
、new B
、new C()
和new C
一样对对象进行默认初始化,但不是对new A
。也就是说,在C++98中,当以下任一条件满足时,总是会执行默认初始化:1)类是非POD类型且缺少初始化程序,或者2)初始化程序为()
. 如果对象是POD类型,则默认初始化会将其零初始化,但对于非POD类型的对象,默认构造函数将被调用。 - Johannes Schaub - litbB obj{};
会将对象值初始化(为0),而不是使用 B obj;
进行默认初始化(垃圾值)。 - legends2knew A
会给成员变量赋一个不确定的值,而使用new A()
则会将成员变量初始化为0……除非A
有一个析构函数的定义,这种情况下两个表达式都会给成员变量赋一个不确定的值……除非A
也有一个构造函数的定义,这种情况下两个表达式都会将成员变量初始化为0……但如果是C++03编译器,那么new A()
将执行“值初始化”,这与零初始化略有不同。 - BlueRaja - Danny Pflughoeftnew 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之间发生了变化。请参阅Michael Burr的解释,了解其如何以及为什么会发生变化。
Test t; // create a Test called t
并且
Test t(); // declare a function called t which returns a 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{}
一样可以用于构造临时的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)
我在下面写了一些示例代码,作为Michael Burr的答案的补充:
#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
*/
8.5.2.4/18:
创建类型为T
的新表达式将按以下方式初始化该对象:
11.6/11:
An object whose initializer is an empty set of parentheses, i.e.,
()
, shall be value-initialized.[Note: Since
()
is not permitted by the syntax for initializer,
X a();
is not the declaration of an object of class
X
, but the declaration of a function taking no argument and returning anX
. The form()
is permitted in certain other initialization contexts (8.5.2.4, 8.5.1.3, 15.6.2). - end note]
同样在11.6/(17.4)中:
- 如果初始化器是
()
,则对象将进行值初始化。
因此,答案是()
将对该对象进行值初始化,而另一个(没有显式初始化器)将对该对象进行默认初始化。
11.6/8:
对于类型为T
的对象进行值初始化意味着:
T
是一个(可能带有cv限定符的)类类型,没有默认构造函数或者默认构造函数是用户提供的或已删除,则该对象将被默认初始化;T
是一个(可能带有cv限定符的)类类型,没有用户提供的或已删除的默认构造函数,则该对象将被零初始化,并检查默认初始化的语义约束,如果T
具有非平凡的默认构造函数,则该对象将被默认初始化;T
是一个数组类型,则每个元素都将被值初始化;11.6/7:
默认初始化类型为T
的对象意味着:
T
是一个(可能带有cv限定符的)类类型,则会考虑构造函数。适用的构造函数被枚举,并通过重载决议选择最佳的初始化器()
。因此,所选的构造函数将被调用,使用空参数列表来初始化对象。T
是数组类型,则每个元素都会进行默认初始化。