类型名称后面的括号对new有影响吗?

1111

如果“Test”是一个普通类,下面两种写法有什么区别:

Test* test = new Test;

并且

Test* test = new Test();

2
这与https://dev59.com/aXI-5IYBdhLWcg3w6tFR有关(但并非完全相同)。 - Steve Jessop
1
只需使用 new Test() 来确保它被初始化为零。 - Sung
8个回答

1031

让我们变得迂腐一些,因为有些差异实际上会影响到你的代码行为。以下大部分内容摘自《旧日新事》(Old New Thing)文章的评论。

有时,new运算符返回的内存将被初始化,有时不会,这取决于你要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() - 对A进行值初始化,因为它是一个POD类型,所以会进行零初始化。

  • new B - 默认初始化(B::m保持未初始化状态)

  • new B() - 对B进行值初始化,因为其默认构造函数是由编译器生成的而不是用户定义的,所以会对所有字段进行零初始化。

  • new C - 默认初始化C,调用默认构造函数。

  • new C() - 对C进行值初始化,调用默认构造函数。

因此,在所有版本的C++中,new Anew A()之间存在差异,因为A是一个POD类型。

而对于new B()这种情况,在C++98和C++03之间存在行为上的差异。

这是C++中一个让人抓狂的尘封角落之一。在构造对象时,有时你想/需要使用括号,有时绝对不能使用,有时则无关紧要。

4
在C++98中,new A()将像new B()new Bnew C()new C一样对对象进行默认初始化,但不是new A。也就是说,在C++98中,当以下任一条件满足时,总是会执行默认初始化:1)类是非POD类型且缺少初始化程序,或者2)初始化程序为(). 如果对象是POD类型,则默认初始化会将其零初始化,但对于非POD类型的对象,默认构造函数将被调用。 - Johannes Schaub - litb
140
有人可以补充一下C++11现在的情况吗? - legends2k
10
使用C++11,你也可以在栈上实现这个功能;B obj{};会将对象值初始化(为0),而不是使用 B obj; 进行默认初始化(垃圾值)。 - legends2k
7
有时候您说“绝对不能加括号”,请问什么情况下无法添加括号? - kec
13
简单来说,使用new A会给成员变量赋一个不确定的值,而使用new A()则会将成员变量初始化为0……除非A有一个析构函数的定义,这种情况下两个表达式都会给成员变量赋一个不确定的值……除非A也有一个构造函数的定义,这种情况下两个表达式都会将成员变量初始化为0……但如果是C++03编译器,那么new A()将执行“值初始化”,这与零初始化略有不同。 - BlueRaja - Danny Pflughoeft
显示剩余23条评论

91

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的解释,了解其如何以及为什么会发生变化。


22
一般来说,第一种情况下我们进行默认初始化,第二种情况下进行值初始化。
例如: 对于int(POD类型)的情况:
- `int* test = new int` - 我们没有进行任何初始化,*test的值可以是任意的。 - `int* test = new int()` - *test的值将为0。
接下来的行为取决于您的Test类型。 我们有不同的情况:Test具有默认构造函数,Test具有生成的默认构造函数,Test包含POD成员,非POD成员...

18
不,它们是相同的。但是有以下区别:

Test t;      // create a Test called t

并且

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

这是由于基本的C++(和C)规则:如果某个东西可能是一个声明,那么它就是一个声明。
编辑:关于POD和非POD数据的初始化问题,虽然我同意已经说过的一切,但我想指出的是,只有当被new或者其他方式构造的东西没有用户定义的构造函数时,这些问题才会出现。如果有这样的构造函数,它将被使用。对于99.99%的合理设计的类来说,都会有这样的构造函数,因此可以忽略这些问题。

22
请注意,这是一个非常重要的观点,因为语句 "Test t(5);" 相当于 "Test t = Test(5);",但是 "Test t();" 和 "Test t = Test();" 是非常不同的。 +1 - ojrac
11
我不同意你的说法,认为问题不能被忽视。虽然不必精确知道规则,但在可能需要新建一个没有用户定义默认构造函数的类时,应该意识到这个问题(然后你要么编写构造函数,要么查找规则)。 - avakar
12
已知答案不正确,打-1分。你的编辑忽略了由之前不理解/不使用构造函数的C程序员编写的代码。 - Tom
5
像struct point { float v[3]; };这样的类,如果加入构造函数反而不好,因为它会破坏POD和聚合体的良好特性。因此,在我看来,“可以忽略这些问题”是不正确的说法。 - me22
6
但它们不是一样的。这个回答完全错误。应该修复或删除,因为根据高赞数来看,它似乎引起了一些混淆。 - juanchopanza
显示剩余5条评论

11
假设Test是一个定义了构造函数的类,那么这两种写法没有区别。后一种形式使得Test的构造函数正在运行更加明显一些,但仅此而已。

4
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)

(这个答案融合了 C++11 中的概念性变化,而当前排名第一的答案没有; 特别是,现在技术上会默认初始化一个标量或 POD 实例,最终会导致其值不确定(对于 POD 类型,技术上会调用一个平凡的默认构造函数)。虽然这并不会对行为产生太多实际上的变化,但确实简化了规则。)

2

我在下面写了一些示例代码,作为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
*/

0
根据 n4713

8.5.2.4/18:

创建类型为 T 的新表达式将按以下方式初始化该对象:
  • 如果省略了 new-initializer,则对象将进行默认初始化(11.6)。
  • 否则,new-initializer 将根据直接初始化的 11.6 的初始化规则进行解释。

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 an X. 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是数组类型,则每个元素都会进行默认初始化。
  • 否则,不执行任何初始化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接