C++默认构造函数

C++ Default constructor

本文关键字:构造函数 默认 C++      更新时间:2023-10-16

给定以下代码:

class temp
{
public:
string str;
int num;
};
int main()
{
temp temp1;
temp temp2 = temp();
cout << temp1.str << endl; //Print ""
cout << temp2.str << endl; //Print ""
cout << temp1.num << endl; //Print a rand num
cout << temp2.num << endl; //Print 0
}

这两者有什么不同—

temp temp1;

temp temp2 = temp();
temp temp1;

这在名为temp1的实例上调用temp的默认构造函数。

temp temp2 = temp();

这在临时对象上调用temp的默认构造函数,然后在temp2上以临时对象为参数调用编译器生成的副本构造函数(当然,这假设编译器不会消除副本;这取决于编译器的优化设置)。

至于为什么会得到不同的初始化值,标准的第8.5节是相关的:


8.5初始化程序[dcl.init]

第5段:

零初始化,类型为T的对象意味着:
  • 如果T是标量类型(3.9),则将对象设置为转换为T的值0(零)
  • 如果T是非并集类类型,则每个非静态数据成员和每个基类子对象被零初始化
  • 如果T是并集类型,则对象的第一个命名数据成员被零初始化
  • 如果T是数组类型,则每个元素被零初始化
  • 如果T是引用类型,则不执行初始化

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

  • 如果T是非POD类类型(子句9),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化格式错误)
  • 如果T是数组类型,则默认初始化每个元素
  • 否则,对象初始化为零

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

  • 如果T是具有用户声明构造函数(12.1)的类类型(第9条),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化格式错误)
  • 如果T是没有用户声明构造函数的非并集类类型,则T的每个非静态数据成员和基类组件都被值初始化
  • 如果T是数组类型,则每个元素都被值初始化
  • 否则,对象初始化为零

第7段:

初始化器是一组空括号的对象,即(),应进行值初始化。

第9段:

如果没有为对象指定初始化器,并且对象是(可能是cv限定的)非POD类类型(或其数组),则该对象应默认初始化;如果对象是const限定类型,则底层类类型应具有用户声明的默认构造函数。否则,如果没有为非静态对象指定初始值设定项,则该对象及其子对象(如果有的话)具有不确定的初始值;如果该对象或其任何子对象是const限定类型,则表示程序格式不正确。

12特殊成员功能[特殊]

第7段:

当类用于创建其类类型(1.8)的对象时,会隐式定义该类的隐式声明默认构造函数。隐式定义的默认构造函数执行该类的初始化集,该初始化集将由用户为该类编写的默认构造函数使用空的mem初始值设定项列表(12.6.2)和空的函数体来执行。

12.6.2初始化基和成员[class.base.init]

第4段:

如果给定的非静态数据成员或基类不是由mem初始化器id命名的(包括由于构造函数没有ctor初始化器而没有mem初始化程序列表的情况),则
  • 如果实体是(可能是cv限定的)类类型(或其数组)或基类的非静态数据成员,并且实体类是非POD类,则该实体将被默认初始化(8.5)。如果实体是const限定类型的非静态数据成员,则实体类应具有用户声明的默认构造函数
  • 否则,实体不会初始化。如果实体是const限定类型或引用类型,或者(可能是cv限定的)POD类类型(或其数组)包含(直接或间接)const限定型的成员,则程序格式错误

既然规则已经制定好了,让我们看看它们是如何应用的:

temp temp1;

temp是一个非POD类型(因为它有一个std::string成员),并且由于没有为temp1指定初始化器,它将被默认初始化(8.5/9)。这调用默认构造函数(8.5/5)。temp有一个隐式默认构造函数(12/7),它默认初始化std::string成员,而int成员根本没有初始化(12.6.2/4)。

temp temp2 = temp();

另一方面,临时temp对象被值初始化(8.5/7),该值初始化所有数据成员(8.5/5),它调用std::string成员中的默认构造函数,零初始化int成员(8.5%5)

当然,如果您不想在5个以上不同的地方引用该标准,只需确保显式初始化所有内容(例如int i = 0;或使用初始化器列表)。

代码的行为在很大程度上取决于所使用的编译器。更确切地说,它取决于编译器实现的语言规范的版本。

对于C++98编译器,两个声明对所声明对象的最终值具有相同的影响:str成员应为空,而num成员应包含不可预测的值。在这两种情况下,实际初始化都是由temp类的编译器提供的默认构造函数执行的默认初始化。默认构造函数初始化str,但未初始化num

对于C++03编译器,其行为是不同的。temp1对象没有区别(它的num仍然是不可预测的)。但CCD_ 40的初始化处理方式不同。在C++03中,()初始化器触发了一种新的初始化,即所谓的值初始化。值初始化忽略编译器提供的顶级对象的默认构造函数,而是直接处理其子对象(本例中为数据成员)。因此,temp2对象通过值初始化有效地初始化,这也将num成员设置为零(除了用空字符串初始化str之外)。因此,temp2.num在C++03编译器中最终为零。

如果在您的实验中,您在temp2.num中观察到一致的零,则意味着您的编译器在这方面遵循C++03规范。

temp temp1;

将创建一个默认初始化的temp对象。由于您没有为temp提供默认构造函数,因此temp的每个成员也将被默认初始化。由于std::string提供了一个默认的ctor,因此它可以正确初始化,并具有一个定义良好的值。然而,该整数会被默认初始化,这是实现定义的,通常是一个随机值。

temp temp2 = temp();

这将首先创建一个值初始化的temp对象。这一点很重要,因为对象本身是值初始化的,其成员也是值初始化的。这对字符串来说并不重要,因为默认值和值初始化是一样的,但对整数来说很重要。值初始化的整数具有值0
然后,您只需将这些成员复制到temp2中即可。

此外,您可能会对这个相关问题感兴趣
编辑:请参阅我对@In silico的回答的评论,了解为什么MSVC不是这样。:/