在C ++中使用花括号实例化对象是什么意思?

时间:2009-04-21 22:20:41

标签: c++ data-structures constructor

假设我有一个结构定义为:

typedef
struct number{
    int areaCode;
    int prefix;
    int suffix;
} PhoneNumber;

当我创建此结构的实例时,如果我使用以下语法:

PhoneNumber homePhone = {858, 555, 1234};

...它正在调用哪个构造函数?默认构造函数或复制构造函数,或者根本没有,因为它没有调用'new'?

这个问题的真正目的是弄清楚如何添加第四个字段。所以我想重新定义我的结构:

typedef
struct number{
    int areaCode;
    int prefix;
    int suffix;
    int extension; // NEW FIELD INTRODUCED
} PhoneNumber;

现在,我可以用FOUR字段创建新的PhoneNumber对象:

PhoneNumber officePhone = {858, 555, 6789, 777}

但是,我已经创建了数百个这样的PhoneNumber实例,只有3个字段(xxx,xxx,xxxx)。因此,我不想通过修改已定义的PhoneNumber对象的每个单一实例化。我希望能够独自留下这些,但仍然能够使用FOUR字段创建新的电话号码实例。所以我试图找出如何覆盖构造函数,以便我现有的三参数实例化不会中断,但它也将支持我的新的四参数实例化。

当我尝试定义一个覆盖3个字段并将第四个设置为默认值'0'的覆盖默认构造函数时,我得到错误(在代码的实例化部分,而不是构造函数定义)抱怨我的对象必须由构造函数初始化,而不是由{...}初始化。所以,如果我覆盖默认构造函数,我似乎不能再使用花括号来创建我的新对象了?

很抱歉,如果这完全偏离了原来的问题。

7 个答案:

答案 0 :(得分:4)

成员实际上是复制初始化的。不调用每个默认构造函数,并且不涉及operator=,这与其他一些答案所暗示的相反。它可以通过名为geordi的软件显示 - 或者通过阅读标准来显示。我将展示使用该软件的“有趣”方式。它有一个类tracked::B,它可以在调用构造函数/复制构造函数或析构函数/复制赋值运算符时向我们显示。它显示的输出是(TRACK限制跟踪它后面的语句):

B1*(B0) B1~

我使用了这段代码

struct T { tracked::B b; }; int main() { tracked::B b; TRACK T t = { b };  }

如您所见,第二个B对象(它是局部变量t中的成员)是从另一个对象b初始化的。当然,没有激活赋值运算符。如果您愿意,可以在标准12.6.1/2中阅读相关内容。

顺便说一句,对于数组(同样是聚合)也是如此。许多人认为作为数组成员的对象必须具有具有默认构造函数的类型。但事实并非如此。它们只能由其他类型的对象进行初始化,并且可以正常工作。

未在聚合中显式初始化的所有其他元素都是初始值。值初始化是默认初始化和零初始化的混合。实际上,如果成员的类型具有用户声明的构造函数,则调用该构造函数。如果它具有 not 具有用户声明的构造函数的类型,则它的每个成员都是值初始化的。对于内置类型(int,bool,pointers,...),值初始化与零初始化相同(这意味着这样的变量将变为零)。以下将每个成员初始化为零 - 除了第一个(a),它将是一个:

struct T { int a, b, c; }; int main() { T t = { 1 }; }

这些初始化规则确实很可怕 - 特别是因为2003年版的C ++引入了值初始化。截至1998年,它是wasn't part of the Standard。如果您对这些括号内的初始化更感兴趣,可以阅读How to initialize nested structures in C++?

答案 1 :(得分:3)

正如其他人写的那样,它没有调用默认的ctor。从概念上讲它是一样的,但在实践中,你将在汇编代码中找不到函数调用。

相反,成员仍未初始化;你正在用花括号结构初始化它们。

有趣的是,这个:

PhoneNumber homePhone = {858, 555, 1234};

此程序集中的结果(GCC 4.0.1,-O0):

movl  $858, -20(%ebp)
movl  $555, -16(%ebp)
movl  $1234, -12(%ebp)

那里没有多少惊喜。程序集内联包含上述C ++语句的函数。将值(以$开头)移动(movl)到堆栈中的偏移量(ebp寄存器)。它们是负面的,因为struct成员的内存位置在初始化代码之前。

如果你没有完全初始化结构,即省略一些像这样的成员:

PhoneNumber homePhone = {858, 555};

...然后我得到以下汇编代码:

movl  $0, -20(%ebp)
movl  $0, -16(%ebp)
movl  $0, -12(%ebp)
movl  $858, -20(%ebp)
movl  $555, -16(%ebp)

似乎编译器实际上执行的操作非常类似于调用默认构造函数,然后是赋值。但同样,这是在调用函数中内联,而不是函数调用。

另一方面,如果你定义一个默认构造函数来初始化成员,给定值,如下所示:

struct PhoneNumber {
  PhoneNumber()
    : areaCode(858)
    , prefix(555)
    , suffix(1234)
  {
  }

  int areaCode;
  int prefix;
  int suffix;
};

PhoneNumber homePhone;

然后你获得实际调用函数的汇编代码,并通过指向struct的指针初始化数据成员:

movl  8(%ebp), %eax
movl  $858, (%eax)
movl  8(%ebp), %eax
movl  $555, 4(%eax)
movl  8(%ebp), %eax
movl  $1234, 8(%eax)

每行movl 8(%ebp), %eax将指针值(eax寄存器)设置为struct数据的开头。在其他行中,eax直接使用,偏移量为4,偏移量为8,类似于前两个示例中的直接堆栈寻址。

当然所有这些都是编译器实现所特有的,但如果其他编译器做了一些非常不同的事情,我会感到惊讶。

答案 2 :(得分:2)

C ++中的结构就像一个类。正在调用默认构造函数。然后,使用其赋值运算符复制每个字段。

答案 3 :(得分:2)

至少在我看来,初始化是C ++标准中最棘手的部分之一。您正在使用的语法:Aggregate x = { 1, 2, 3, 4 };定义聚合类型的初始化,前四个成员分配值1,2,3和4.

作为特定情况的注释(结构从3个元素增长到4个元素),花括号之间的初始化列表中不存在的字段将是值初始化,特别是标量类型等同于零初始化,它本身与赋值0相同。因此,您的第四个元素将初始化为0.

<强>参考

它全部在C ++标准的第8.5章中定义,更确切地说在8.5.1 Aggregates中定义。不会使用任何隐式声明的默认构造函数初始化聚合。如果使用上面的语法,则要求编译器使用提供的值初始化给定字段。聚合中的任何额外字段都应值初始化

现在,如果类型是具有此类构造函数的类,值初始化被定义为对用户定义的默认构造函数的调用。如果它是没有用户定义构造函数的数组或非联合类,则每个成员属性都将值初始化。否则,对象将零初始化,然后再次定义为...

零初始化定义为将标量类型的值设置为0。对于类,每个类数据成员和基类将零初始化。对于联盟,第一个成员(仅)将零初始化,对于数组,所有成员将零初始化

答案 4 :(得分:1)

它有效地调用了默认的ctor;发生的事情是分配一个结构,并为每个值分配使用默认值“=”。

答案 5 :(得分:0)

  

但是,我有数百个   已创建PhoneNumber实例   只有3个字段(xxx,xxx,xxxx)。   所以我不想经历和   修改每个单独的实例化   我的PhoneNumber对象已经存在   定义

没问题,你只需要调用update-instance-for-redefined-class和.....呃,没关系。继续标记这个“无益”

答案 6 :(得分:0)

您的类型没有构造函数,因此需要在聚合初始化站点修改现有的三参数语法。

除非您新添加的字段的语义,即。新的字段类型,'归零设计意识'(.NET编程推荐的成语,Brad和Co等设计指南无意义),你

不能:

a)提供一些更有意义的东西作为C ++默认参数,如果有一些魔法,则涉及一个方法(很快就可以在杂货店Web 4.0附近的大众市场C#商店和旧COM中使用deault / optional params)

你也不能

b)遵循C#类似设计值类型的模式,将无效值标记设置为0(在这种情况下,如果您无法控制常量,这在一般情况下可能非常糟糕和糟糕 - 某些源级库非常有用以及所有类似JavaTM MS的框架都很糟糕。

简而言之,如果0是一个有效值,那么大多数编译器编写者在任何托管样式或习惯用法存在之前都认为它是有用的;但它不一定正确。

无论如何,你最好的机会不是C ++或C#。它是代码生成,即。比现代C ++'模板'推动更自由的元编程..你会发现它类似于JSON数组注释,可以使用精神或(我的建议是)滚动你自己的工具。从长远来看,它会有所帮助,最终你会想要做得更好(即他们称之为蹩脚的人造型,也就是.NET中的奥斯陆)。

[像这样的问题遍及多种语言,它是所有C风格的缺点(C,C ++,Java,C#,你的名字);非常类似于任何类型的跨域或语义I / O工作所需的数组hackery,甚至在像SOAP等网络技术中也很明显。新的C ++ 0x可变参数位在这里也没有多大帮助,但显然可以如果您选择使用某些模板hackery,则会使编译时间快得多。谁在乎:)]