提问人:David Read 提问时间:3/7/2009 最后编辑:Paul MaserratDavid Read 更新时间:6/7/2023 访问量:138316
类型名称后面的括号是否与 new 有区别?
Do the parentheses after the type name make a difference with new?
问:
如果“测试”是一个普通的类,那么两者之间有什么区别吗?
Test* test = new Test;
和
Test* test = new Test();
答:
不,它们是一样的。但两者之间是有区别的:
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 的构造函数正在运行更清楚一些,但仅此而已。
一般来说,第一种情况下我们有默认初始化,第二种情况下有值初始化。
例如: 如果是 int(POD 类型):
int* test = new int
- 我们没有任何初始化,*test 的值可以是任何值。int* test = new int()
- *test 的值为 0。
下一个行为取决于您的类型测试。 我们有不同的案例:测试有 defult 构造函数、测试生成了默认构造函数、测试包含 POD 成员、非 POD 成员......
让我们变得迂腐,因为有些差异实际上会影响代码的行为。以下大部分内容摘自对 Old New Thing 文章的评论
有时,new 运算符返回的内存将被初始化,有时则不会,这取决于您要更新的类型是 POD(纯旧数据),还是包含 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 编译器中,应发生以下情况:
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
- default-initializes(使 B::m 未初始化)new B()
- value-initializes B 对所有字段进行零初始化,因为它的默认 ctor 是编译器生成的,而不是用户定义的。new C
- default-initializes C,它调用默认的 ctor。new C()
- value-初始化 C,它调用默认的 ctor。
因此,在所有版本的 C++ 中,和之间存在差异,因为 A 是 POD。new A
new A()
在这种情况下,C++98 和 C++03 之间的行为存在差异。new B()
这是 C++ 中尘土飞扬的角落之一,可以让你发疯。在构造一个对象时,有时你想要/需要parens,有时你绝对不能拥有它们,有时这并不重要。
评论
new A()
new B()
new B
new C()
new C
new A
()
B obj{};
B obj;
new A
new A()
A
A
new A()
new Thing();
明确表示您想要调用构造函数,而表示您不介意不调用构造函数。new Thing;
如果在具有用户定义构造函数的结构/类上使用,则没有区别。如果在一个简单的结构/类(例如)上调用,那么 is like while is like - 初始化为零。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;
};
在这种情况下,vs 的行为在 C++98 和 C++2003 之间发生了变化。参见 Michael Burr 的解释,了解其方式和原因。new Thingy;
new Thingy();
的规则类似于使用自动存储持续时间初始化对象时发生的情况(尽管由于令人烦恼的解析,语法可能略有不同)。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:调用默认构造函数,无论它是值初始化(带 )还是默认初始化(不带)。(值初始化还有一些额外的先验归零行为,但默认构造函数始终拥有最终决定权。
{}
{}
- NO:被用过吗?
{}
- YES:对象是值初始化的,对于非类类型,它或多或少只是零初始化。
- NO:对象是默认初始化的,对于非类类型,这会给它留下一个不确定的值(它实际上没有初始化)。
- YES:调用默认构造函数,无论它是值初始化(带 )还是默认初始化(不带)。(值初始化还有一些额外的先验归零行为,但默认构造函数始终拥有最终决定权。
这些规则精确地转换为语法,并添加了可以替换的规则,因为永远不会被解析为函数声明。所以: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
*/
根据 n4713:
8.5.2.4/18:
创建类型对象的 new-expression 将初始化该对象,如下所示:
T
- 如果省略 new-initializer,则对象将默认初始化 (11.6)。
- 否则,将根据 11.6 的初始化规则解释 new-initializer 进行直接初始化。
11.6/11:
其初始值设定项为一组空括号(即 )的对象应进行值初始化。
()
[注意:由于初始值设定项的语法不允许,
()
X a();
不是类对象的声明,而是函数的声明,不带参数并返回一个。在某些其他初始化上下文(8.5.2.4、8.5.1.3、15.6.2)中允许使用该表单。- 尾注]
X
X
()
同样在 11.6/(17.4) 中:
- 如果初始值设定项是 ,则对象将值初始化。
()
因此,答案是将对该对象进行值初始化,而另一个对象(没有显式初始值设定项)将默认初始化该对象。()
11.6/8:
对 Type 的对象进行值初始化意味着:
T
- 如果是(可能是 CV 限定的)类类型,并且没有默认构造函数或用户提供或删除的默认构造函数,则对象是默认初始化的;
T
- 如果是(可能是 cv 限定的)类类型,没有用户提供或删除的默认构造函数,则对象初始化为零,并检查默认初始化的语义约束,如果具有非平凡的默认构造函数,则对象默认初始化;
T
T
- 如果是数组类型,则每个元素都初始化值;
T
- 否则,对象初始化为零。
11.6/7:
默认初始化类型的对象意味着:
T
- 如果是(可能是 cv 限定的)类类型,则考虑构造函数。枚举适用的构造函数,并通过重载解析选择初始值设定项的最佳构造函数。使用空参数列表调用这样选择的构造函数来初始化对象。
T
()
- 如果是数组类型,则每个元素都是默认初始化的。
T
- 否则,不执行任何初始化。
评论