类型名称后面的括号是否与 new 有区别?

Do the parentheses after the type name make a difference with new?

提问人:David Read 提问时间:3/7/2009 最后编辑:Paul MaserratDavid Read 更新时间:6/7/2023 访问量:138316

问:

如果“测试”是一个普通的类,那么两者之间有什么区别吗?

Test* test = new Test;

Test* test = new Test();
C 构造函数 初始化 new-operator c++-faq

评论

2赞 Steve Jessop 12/8/2010
这与 stackoverflow.com/questions/1613341/ 有关(但不完全相同)......
1赞 Sung 7/6/2016
只需使用 new Test() 来确保它是零初始化的

答:

18赞 anon 3/7/2009 #1

不,它们是一样的。但两者之间是有区别的:

Test t;      // create a Test called t

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

这是因为基本的 C++(和 C)规则:如果某物可能是声明,那么它就是声明。

编辑:关于 POD 和非 POD 数据的初始化问题,虽然我同意所说的一切,但我只想指出,这些问题仅适用于新事物或以其他方式构造的事物没有用户定义的构造函数。如果有这样的构造函数,它将被使用。对于 99.99% 的合理设计的类,都会有这样的构造函数,因此可以忽略这些问题。

评论

22赞 ojrac 3/7/2009
请注意,这一点特别重要,因为行 “Test t(5);” 等价于 “Test t = Test(5);” -- 但 “Test t();” 与 “Test t = Test();” 有很大不同。+1
11赞 avakar 3/6/2010
-1,我不同意你关于这些问题可以忽略的说法。您不必精确地了解这些规则,但您应该了解这些规则,以防您必须在没有用户定义的默认构造函数的情况下新建类(然后您应该编写构造函数或查找规则)。
12赞 Tom 4/16/2010
-1 表示已知的错误答案。您的编辑忽略了不理解/使用构造函数的前 C 程序员编写的代码的存在。
5赞 me22 1/2/2011
像struct point { float v[3];对于这样的事情,构造函数将是一个坏主意,因为它会阻止 POD 和聚合带来的所有漂亮属性。所以“问题可以忽略不计”是错误的,imo。
6赞 juanchopanza 8/11/2014
但它们并不相同。这个答案是完全错误的。它应该被修复或删除,因为从大量的赞成票来看,它似乎引起了一些混乱。
11赞 Evan Shaw 3/7/2009 #2

假设 Test 是具有已定义构造函数的类,则没有区别。后一种形式使 Test 的构造函数正在运行更清楚一些,但仅此而已。

22赞 bayda 3/7/2009 #3

一般来说,第一种情况下我们有默认初始化,第二种情况下有值初始化。

例如: 如果是 int(POD 类型):

  • int* test = new int- 我们没有任何初始化,*test 的值可以是任何值。

  • int* test = new int()- *test 的值为 0。

下一个行为取决于您的类型测试。 我们有不同的案例:测试有 defult 构造函数、测试生成了默认构造函数、测试包含 POD 成员、非 POD 成员......

1033赞 Michael Burr 3/7/2009 #4

让我们变得迂腐,因为有些差异实际上会影响代码的行为。以下大部分内容摘自对 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 Anew A()

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

这是 C++ 中尘土飞扬的角落之一,可以让你发疯。在构造一个对象时,有时你想要/需要parens,有时你绝对不能拥有它们,有时这并不重要。

评论

4赞 Johannes Schaub - litb 1/2/2011
@j_random_hacker,将在 C++98 中默认初始化对象,就像它对 、 和 所做的那样,但不会对 进行初始化。也就是说,在以下任一情况下,默认初始化始终在 C++98 中完成:1) 类是非 POD 并且缺少初始值设定项,或者 2) 初始值设定项是 。default-initialization 零 - 如果对象是 POD,则初始化对象,但对非 POD 调用默认构造函数。new A()new B()new Bnew C()new Cnew A()
141赞 legends2k 8/21/2012
有人可以添加 C++11 现在的情况吗?
10赞 legends2k 5/7/2013
@Jon:使用 C++11,您也可以在堆栈中执行此操作; 将使对象值初始化(到 0s),而不是默认初始化(垃圾)。B obj{};B obj;
7赞 kec 4/26/2015
你说“有时你绝对不能拥有它们[括号]”。在什么情况下无法添加它们?
13赞 BlueRaja - Danny Pflughoeft 3/7/2017
所以tl;dr 为成员提供不确定的值并将成员值初始化为 0...除非定义了析构函数,在这种情况下,两个表达式都会给成员提供不确定的值......除非还定义了构造函数,在这种情况下,两个表达式都对成员进行零初始化...除非它是 C++03 编译器,在这种情况下,将“值初始化”成员,这在某种程度上是不同的(?)。就是这么简单。new Anew A()AAnew A()
92赞 kfsone 2/16/2013 #5

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();

4赞 ThatsJustCheesy 8/20/2020 #6

的规则类似于使用自动存储持续时间初始化对象时发生的情况(尽管由于令人烦恼的解析,语法可能略有不同)。new

如果我说:

int my_int; // default-initialize → indeterminate (non-class type)

然后具有不确定的值,因为它是非类类型。或者,我可以像这样进行值初始化(对于非类类型,零初始化):my_intmy_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:对象是默认初始化的,对于非类类型,这会给它留下一个不确定的值(它实际上没有初始化)。

这些规则精确地转换为语法,并添加了可以替换的规则,因为永远不会被解析为函数声明。所以: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赞 uqb 9/1/2021 #7

我在下面写了一些示例代码,作为对 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赞 GKxx 3/24/2023 #8

根据 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)中允许使用该表单。- 尾注]XX()

同样在 11.6/(17.4) 中:

  • 如果初始值设定项是 ,则对象将值初始化。()

因此,答案是将对该对象进行值初始化,而另一个对象(没有显式初始值设定项)将默认初始化该对象。()

11.6/8:

对 Type 的对象进行值初始化意味着:T

  • 如果是(可能是 CV 限定的)类类型,并且没有默认构造函数或用户提供或删除的默认构造函数,则对象是默认初始化的;T
  • 如果是(可能是 cv 限定的)类类型,没有用户提供或删除的默认构造函数,则对象初始化为零,并检查默认初始化的语义约束,如果具有非平凡的默认构造函数,则对象默认初始化;TT
  • 如果是数组类型,则每个元素都初始化值;T
  • 否则,对象初始化为零。

11.6/7:

默认初始化类型的对象意味着:T

  • 如果是(可能是 cv 限定的)类类型,则考虑构造函数。枚举适用的构造函数,并通过重载解析选择初始值设定项的最佳构造函数。使用空参数列表调用这样选择的构造函数来初始化对象。T()
  • 如果是数组类型,则每个元素都是默认初始化的。T
  • 否则,不执行任何初始化。