构造函数内部发生了什么,编译器如何执行并提供默认构造函数?

4

我希望了解编译器提供的构造函数的作用是什么?这个构造函数是否负责内存分配以及创建对象所需的所有内容。

我并不是从成员变量初始化的角度来问这个问题。我想知道默认构造函数在编译器执行和提供的过程中发生了什么。

4个回答

5

默认构造函数是指没有参数或者所有参数都有默认值的构造函数。

如果一个类A没有用户定义的构造函数并且需要一个构造函数,编译器会隐式声明一个默认无参构造函数 A::A()。这个构造函数是其类的内联公共成员。当编译器使用这个构造函数创建类型为A的对象时,编译器会隐式定义 A::A()。这个构造函数将没有构造函数初始化程序和空的主体。

在定义A的隐式声明构造函数之前,编译器首先定义A的基类和非静态数据成员的隐式声明构造函数。如果一个类A有任何常量或引用类型成员,则不会创建默认构造函数。

如果一个类A的构造函数满足以下条件,则它是平凡的:

* It is implicitly defined
* A has no virtual functions and no virtual base classes
* All the direct base classes of A have trivial constructors
* The classes of all the nonstatic data members of A have trivial constructors

如果上述任何内容不符合要求,则构造函数是非平凡的。
联合成员不能是具有非平凡构造函数的类类型。
像所有函数一样,构造函数可以有默认参数。它们用于初始化成员对象。如果提供了默认值,则可以在构造函数的表达式列表中省略尾随参数。请注意,如果构造函数具有任何没有默认值的参数,则它不是默认构造函数。
阅读 默认构造函数(仅限C ++)

Phoenix,感谢您回答了上面的一些问题 :) ... 但我想知道在对象构建期间编译器在创建完整对象之前做了什么。您的答案是在对象构建之后。 - Mahesh
1
虽然IBM的参考通常都很好,但那不是一个好的定义。 - Martin York
这个构造函数是其类的内联公共成员。即使如此(我需要标准的引用来说服我),它并不是真正相关的。 - Martin York
构造函数将没有构造函数初始化程序 假设您谈论的是初始化列表(否则我道歉),这是明显错误的(除非您谈论的是没有基类和成员的类)。 - Martin York

2

一个默认构造函数。

它没有参数(或者所有参数都有默认值),因此可以构造一个没有任何参数的对象。

如果您没有定义任何构造函数,编译器将为您生成一个默认构造函数。
编译器生成的默认构造函数按以下顺序执行:

  • 调用所有基类的默认构造函数(如果有的话,按顺序)。
  • 调用每个成员的默认构造函数(按照它们在类中声明的顺序)。
    • 如果成员是int/float/char等,则未定义。
    • 如果成员是指针,则未定义。

复制构造函数:

如果您没有定义复制构造函数,则编译器将生成一个默认的复制构造函数。
编译器生成的复制构造函数按以下顺序执行:

  • 调用所有基类的复制构造函数(如果有的话,按顺序)。
  • 调用每个成员的复制构造函数(按照它们在类中声明的顺序)。
    • 将每个成员传递给正在复制的对象的相应成员。
    • 请注意,对于指针,这意味着只需复制指针值。
      这就是为什么当类包含对象管理的RAW指针时,我们会遇到浅层复制问题的原因。

虽然不是构造函数。重要的是要注意,当未定义赋值运算符时,编译器还将自动生成赋值运算符。
编译器生成的赋值运算符按以下顺序执行:

  • 调用每个基类的赋值运算符(如果有的话,按顺序)。
  • 调用每个成员的赋值运算符(按照它们在类中声明的顺序)。
    • 将每个成员传递给正在复制的对象的相应成员。
    • 请注意,对于指针,这意味着只需复制指针值。
      这就是为什么当类包含对象管理的RAW指针时,我们会遇到浅层复制问题的原因。

默认析构函数:
大多数人认为析构函数是微不足道的或不存在的。但是需要注意默认版本实际上做了些什么(它比什么都没有稍微多一点)。

  • 每个成员都调用其析构函数(以相反声明顺序)
  • 请注意,int/float/pointers没有析构函数,因此不会进行任何明确的操作。
  • 每个基类析构函数按相反顺序调用。

如果您定义了一个析构函数,则行为不会改变。但是,作为析构函数的一部分定义的代码在上述定义的行为之前执行。因此,在实际上始终存在析构函数,只是代码为空块。

注意:当使用虚拟基类时,有一些特殊考虑因素。但这是另一个问题。

因此,即使您定义了一个空类,编译器也将始终为您生成四个方法:

class X: public Z
{
    int a;
    Y   b;
    Z*  c;
};

// Compiler generated methods will look like this:
X::X()
  :Z() // Construct base class.
  //,a?? The default construction of an int does nothing the value is undefined.
  ,b()
  //,c?? The default construction of a pointer does nothing,
{}
X::~X()
{} // Note members are destoyed here
   // ~c:  Does nothing it is a pointer.
   // ~b:  destroyes b via Y::~Y()
   // ~a:  Does nothing as POD has not destructr.
   // ~Z(): Destory base class.
X::(X const& rhs)
    :Z(rhs)
    ,a(rhs.a)
    ,b(rhs.b)
    ,c(rhs.c)
{}
X& operator=(X const& rhs)
{
    Z::operator=(rhs);
    a = rhs.a;
    b = rhs.b;
    c = rhs.c;
    return *this;
}

1
编译器在创建完整对象之前会执行以下操作:
- 调用基类的默认构造函数(如果有基类) - 为类的每个数据成员调用默认构造函数(对于每个具有构造函数的数据成员,即非原始类型)... 如果其中一个构造函数抛出异常,则调用已经先前创建的数据成员的析构函数。
“构造函数并不是普通的函数。特别是,它与内存管理例程的交互方式与普通成员函数不同。”:我正在寻找这种类型的答案... 如果有人知道更多,请告诉我?:)
如果你知道更多,请告诉我。
Something* something = new Something();

...然后调用"operator new"(默认的operator new将从堆中分配内存),然后在新分配的内存上调用构造函数。或者,如果您这样做:

Something something;

...然后在堆栈上分配(或保留)一些内存,然后在新分配/保留的内存上调用构造函数。

它类似于其他所有非静态方法(包括析构函数),因为有一个“this”指针:不同之处在于当调用构造函数时,将包含“this”的内存刚刚被分配但尚未初始化(这就是构造函数所做的:它初始化“this”)。


ChrisW,好答案!我想知道构造函数内部会发生什么,以产生默认构造函数。 - Mahesh
我不知道我是否回答/理解了你的问题。 - ChrisW

0
这是我从Stroustrup的书中得到的内容:“构造函数并不完全是普通的函数。特别是,它以普通成员函数不具备的方式与内存管理例程进行交互。”:这正是我在寻找的答案。如果有人知道更多,请告诉我吧? :)

我在我的回答中添加了内容来尝试回答这个问题。 - ChrisW
马丁约克和ChrisW的回答都很出色。+1给两人。但我不能接受两个答案,这不是我的本意。 - Mahesh

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