C ++中是否有隐式默认构造函数?

时间:2009-02-18 22:56:48

标签: c++ constructor

在我正在阅读的那本书(C++ Without Fear)中,它说如果你没有为一个类声明一个默认构造函数,那么编译器会为你提供一个,它“清除每个数据成员”。我已经尝试过这个,而且我没有看到任何归零行为。我也找不到任何在Google上提到这一点的内容。这只是一个错误或特定编译器的怪癖吗?

11 个答案:

答案 0 :(得分:62)

如果您没有定义构造函数,编译器将为您定义默认构造函数。

的实施

默认构造函数是:

  • 默认构造基类(如果基类没有默认构造函数,这是编译失败)
  • 默认按声明顺序构造每个成员变量。 (如果成员没有默认构造函数,则这是编译失败)。

注意:
POD数据(int,float,pointer等)没有显式构造函数,但默认操作是什么都不做(在C ++哲学的风向标中;除非我们明确要求,否则我们不想支付任何费用)

如果没有定义析构函数/复制构造函数/赋值运算符,则编译器会为您构建其中一个(因此类总是有一个析构函数/复制构造函数/赋值运算符(除非您作弊并明确声明一个但不定义它) ))。
默认实现是:

析构函数:

  • 如果定义了用户定义的析构函数,请执行提供的代码。
  • 以声明的相反顺序调用每个成员的析构函数
  • 调用基类的析构函数。

复制构造函数:

  • 调用Base类Copy Constructor。
  • 按声明顺序为每个成员变量调用复制构造函数。

分配操作员:

  • 调用基类赋值运算符
  • 按声明顺序调用每个成员变量的赋值运算符。
  • 返回对此的引用。

注意POD数据的复制构造/赋值操作符只是复制数据(因此与RAW指针相关的浅拷贝问题)。

答案 1 :(得分:37)

我认为值得指出的是,如果您提供无构造函数,则默认构造函数将仅由编译器创建。这意味着如果你只提供一个带参数的构造函数,编译器将为你创建默认的no-arg构造函数。

您的书中讨论的归零行为可能特定于特定编译器。我一直认为它可以变化,你应该明确初始化任何数据成员。

答案 2 :(得分:34)

  
      
  • 编译器是否自动生成默认构造函数?
  •   
  • 隐式生成的默认构造函数是否执行零   初始化?
  •   

如果您在法律上解析2003标准的语言,则答案为。但是,这不是整个故事,因为与用户定义的默认构造函数不同,从头开始创建对象时,并不总是使用隐式定义的默认构造函数是另外两种情况:无构造成员方式值初始化

“无结构”案例实际上只是一种技术性,因为它在功能上与调用普通默认构造函数没什么区别。另一种情况更有趣:通过使用“()”调用成员方式的值初始化[就像显式调用没有参数的构造函数]和它绕过技术上称为的< / em>默认构造函数。相反,它递归地对每个数据成员执行值初始化,对于原始数据类型,这最终解析为<​​strong>零初始化。

因此,编译器提供了两个不同的隐式定义的默认构造函数。其中一个执行执行原始成员数据的零初始化,而另一个则不执行。以下是一些如何调用每种类型的构造函数的示例:

    MyClass a; // default-construction or no construction
    MyClass b = MyClass(); // member-wise value-initialization

    new MyClass; // default-construction or no construction
    new MyClass(); // member-wise value-initialization

注意:如果存在用户声明的默认构造函数 ,那么成员方式的值初始化只会调用它并停止。


以下是有关此标准的详细说明......

  • 如果您没有声明构造函数,编译器会隐式创建默认构造函数[12.1-5]

  • 默认构造函数初始化基元类型[12.1-7]

    MyClass() {} // implicitly defined constructor
    
  • 如果使用“()”初始化对象,则不会直接调用默认构造函数。相反,它引发了一系列称为 value-initialization [8.5-7]

  • 的规则。
  • 值初始化的净效果是隐式声明的默认构造函数永远不会被称为。相反,调用一个递归的成员值初始化,最终零初始化任何原始成员,并在任何具有用户声明的构造函数的成员上调用默认构造函数[8.5-5]

  • 值初始化甚至适用于原始类型 - 它们将被零初始化。 [8.5-5]

    a = int(); // equivalent to int a=0;
    

所有这些对于大多数目的来说都没有实际意义。类的编写者通常不能假设数据成员在隐式初始化序列期间将被清零 - 因此,如果任何自我管理类具有需要初始化的任何原始数据成员,则应该定义自己的构造函数。

那么什么时候重要?

  • 在某些情况下,通用代码可能会强制初始化未知类型。值初始化提供了一种方法。请记住,如果用户提供了构造函数,则不会发生隐式零初始化。

  • 默认情况下,std :: vector包含的数据是值初始化的。这可以防止内存调试器识别与未初始化的内存缓冲区相关的逻辑错误。

    vector::resize( size_type sz, T c=T() ); // default c is "value-initialized"
    
  • 可以使用值初始化语法对整个基元类型或“普通旧数据”(POD)类型结构进行零初始化。

    new int[100]();
    

This post提供了有关标准版本之间差异的更多详细信息,并且还指出了标准在主要编译器中的应用方式不同的情况。

答案 3 :(得分:20)

C ++确实会生成默认构造函数,但前提是您不提供自己的构造函数。该标准没有说明将数据成员归零。默认情况下,当您第一次构造任何对象时,它们是未定义的。

这可能会令人困惑,因为大多数C ++原语类型都有默认的“构造函数”,它们将它们初始化为零(int(),bool(),double(),long(),等。),但是编译器不像对象成员那样将它们调用到初始POD成员。

值得注意的是,STL 确实使用这些构造函数来默认构造包含基本类型的容器的内容。您可以查看this question以获取有关STL容器中的内容如何进入的更多详细信息。

答案 4 :(得分:11)

为类创建的默认构造函数不会初始化内置类型,但会在所有用户定义的成员上调用默认构造函数:

class Foo
{
public:
     int x;
     Foo() : x(1) {}
};

class Bar
{
public:
     int y;
     Foo f;
     Foo *fp;
};

int main()
{

    Bar b1; 
    ASSERT(b1.f.x == 1); 
    // We know nothing about what b1.y is set to, or what b1.fp is set to.

    // The class members' initialization parallels normal stack initialization.
    int y;  
    Foo f; 
    Foo *fp; 
    ASSERT(f.x == 1);
    // We know nothing about what y is set to, or what fp is set to.

}

答案 5 :(得分:5)

如果用户创建的构造函数和析构函数不存在,编译器将生成默认构造函数和析构函数。这些不会修改任何数据成员的状态。

在C ++(和C)中,不保证任何已分配数据的内容。在调试配置中,某些平台会将其设置为已知值(例如0xFEFEFEFE)以帮助识别错误,但不应该依赖它。

答案 6 :(得分:4)

C ++ 保证将内存清零。 Java和C#做(以某种方式说)。

有些编译器可能会,但不依赖于此。

答案 7 :(得分:4)

仅对全局变量进行归零。因此,如果您的对象在全局范围内声明,则其成员将被清零:

class Blah
{
public:
    int x;
    int y;
};

Blah global;

int main(int argc, char **argv) {
    Blah local;
    cout<<global.x<<endl;  // will be 0
    cout<<local.x<<endl;   // will be random
}

答案 8 :(得分:2)

在C ++ 11中,编译器生成的默认构造函数标记为已删除,如果:

  • 该类有一个参考字段
  • 或没有用户定义的默认构造函数的const字段
  • 或没有默认初始值设定项的字段,带有已删除的默认构造函数

http://en.cppreference.com/w/cpp/language/default_constructor

答案 9 :(得分:1)

C ++生成一个默认构造函数。如果需要(我相信在编译时确定),它还将生成默认的复制构造函数和默认的赋值构造函数。我没有听说过关于将内存归零的保证。

答案 10 :(得分:1)

默认情况下,编译器不会生成默认构造函数,除非实现不需要。 所以,基本上构造函数必须是非平凡的构造函数

对于构造函数是非平凡的构造函数,以下是任何人都可以满足的条件:

1)该类具有虚拟成员函数。 2)类成员子对象或基类具有非平凡的构造函数。 3)一个类具有虚拟继承层次结构。