如果'Test'是一个普通的类,有什么区别:
Test* test = new Test;
和
Test* test = new Test();
让我们变得迂腐,因为存在实际上会影响代码行为的差异。以下大部分内容来自对 "Old New Thing" article 的评论。
有时 new 运算符返回的内存将被初始化,有时它不会取决于您要更新的类型是否为 POD (plain old data),或者它是否是包含 POD 成员并使用编译器生成的类默认构造函数。
在 C++1998 中有两种类型的初始化:零和默认
在 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 编译器中,应发生以下情况:
新 A - 不确定值
new A() - 零初始化
new B - 默认构造(B::m 未初始化)
new B() - 默认构造(B::m 未初始化)
新 C - 默认构造(C::m 初始化为零)
new C() - 默认构造(C::m 初始化为零)
在符合 C++03 的编译器中,事情应该是这样的:
新 A - 不确定值
new A() - value-initialize A,这是零初始化,因为它是一个 POD。
new B - 默认初始化(使 B::m 未初始化)
new B() - 对 B 进行值初始化,将所有字段初始化为零,因为它的默认 ctor 是编译器生成的,而不是用户定义的。
new C - 默认初始化 C,它调用默认 ctor。
new C() - 值初始化 C,它调用默认 ctor。
因此,在所有版本的 C++ 中,new A
和 new A()
之间存在差异,因为 A 是 POD。
对于案例 new B()
,C++98 和 C++03 之间的行为存在差异。
这是 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 之间发生了变化。请参阅 Michael Burr's explanation 了解如何以及为什么。
一般来说,我们在第一种情况下进行默认初始化,在第二种情况下进行值初始化。
例如:如果是 int(POD 类型):
int* test = new int - 我们有任何初始化,*test 的值可以是任何值。
int* test = new int() - *test 的值为 0。
下一个行为取决于您的类型测试。我们有不同的情况:测试有默认构造函数,测试生成默认构造函数,测试包含 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
,没有例外。
所以,规则或多或少:
是类类型吗? YES:调用默认构造函数,无论是值初始化(带 {})还是默认初始化(不带 {})。 (值初始化有一些额外的预先归零行为,但默认构造函数总是有最终决定权。)否:是否使用了 {}? YES:对象是值初始化的,对于非类类型,或多或少只是零初始化。 NO:对象是默认初始化的,对于非类类型,它会留下一个不确定的值(实际上它没有被初始化)。
YES:调用默认构造函数,无论是值初始化(带 {})还是默认初始化(不带 {})。 (值初始化有一些额外的预先归零行为,但默认构造函数总是有最终决定权。)
否:是否使用了 {}? YES:对象是值初始化的,对于非类类型,或多或少只是零初始化。 NO:对象是默认初始化的,对于非类类型,它会留下一个不确定的值(实际上它没有被初始化)。
YES:对象是值初始化的,对于非类类型,或多或少只是零初始化。
NO:对象是默认初始化的,对于非类类型,它会留下一个不确定的值(实际上它没有被初始化)。
这些规则精确地转换为 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 类型,在技术上调用一个简单的默认构造函数)。虽然这不会导致行为上的太多实际变化,但它确实在一定程度上简化了规则。)
我在下面写了一些示例代码,作为对 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
*/
new A()
将默认初始化 C++98 中的对象,就像它对new B()
、new B
、new C()
和new C
所做的那样,但 not 与 {6 }。也就是说,在以下情况下,C++98 中始终会执行默认初始化:1) 类是非 POD 并且缺少初始化程序,或者 2) 初始化程序是()
。 default-initialization 如果对象是 POD,则对其进行零初始化,但调用非 POD 的默认构造函数。B obj{};
将使对象值初始化(为 0),而B obj;
将被默认初始化(垃圾)。new A
给成员一个不确定的值,而new A()
将成员值初始化为 0...除非A
定义了析构函数,在这种情况下,两个表达式都给成员不确定的值...除非 { 3} 还定义了一个构造函数,在这种情况下,两个表达式都将成员初始化为零...除非它是 C++03 编译器,在这种情况下new A()
将“值初始化”成员,这在某种程度上有所不同(?) .很简单。