【问题标题】:Is there an implicit default constructor in C++?C++ 中是否存在隐式默认构造函数?
【发布时间】:2010-10-08 11:20:35
【问题描述】:

在我正在阅读的书 (C++ Without Fear) 中,它说如果您不为类声明默认构造函数,编译器会为您提供一个,它“将每个数据成员清零”。我已经对此进行了实验,但没有看到任何归零行为。我在谷歌上也找不到任何提到这一点的东西。这只是特定编译器的错误还是怪癖?

【问题讨论】:

  • 听起来你需要一本更好的书。 ;)
  • 是的。不可原谅的错误,如果“将每个数据编号归零”确实是文字引用。
  • 我认为这是一个常见的 c 误解。在访问此页面之前,我一直在想同样的事情。我确定我在某处读过它,或者我在课堂上学过。
  • 我怀疑当人们得知全局对象保证归零时会出现这种混淆,尽管最好也不要依赖它。

标签: c++ constructor


【解决方案1】:

C++ 确实保证将内存清零。 Java 和 C# 可以(从某种意义上说)。

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

【讨论】:

    【解决方案2】:

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

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

    值得注意的是,STL确实使用这些构造函数来默认构造包含原始类型的容器的内容。您可以查看this question,了解有关 STL 容器中的事物如何被初始化的更多详细信息。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

        • 调试模式下使用的值取决于编译器。而不同的值通常代表不同的状态。像分配/解除分配/未初始化等。
        • 澄清一下,这些标记值通常由内存管理库(运算符 new()、malloc 和派生类)设置,而不是由创建默认构造函数的编译器设置。
        【解决方案5】:

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

        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
        }
        

        【讨论】:

        • 它是标准的一部分。您知道不将全局变量归零的投诉编译器吗?
        【解决方案6】:

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

        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.
        
        }
        

        【讨论】:

          【解决方案7】:

          如果你不定义构造函数,编译器会为你定义一个默认构造函数。

          建设

          这个的实现

          默认构造函数是:

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

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

          复制

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

          析构函数:

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

          复制构造函数:

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

          复制赋值运算符:

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

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

          移动

          如果没有定义析构函数/复制构造函数/复制赋值/移动构造函数/移动赋值运算符,编译器会为您构建移动运算符之一。
          默认实现是:

          隐式声明的移动构造函数 如果没有为类类型(结构、类或联合)提供用户定义的移动构造函数,并且以下所有情况都为真:

          移动构造函数:

          • 调用基类复制构造函数。
          • 按声明顺序为每个成员变量调用移动构造函数。

          移动赋值运算符:

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

          【讨论】:

          • 来自标准中的第 8.5 节第 5 项:“默认初始化 T 类型的对象意味着:... [如果 T 是 POD] 对象被零初始化。” 并来自同一部分的第 4 项:“对 T 类型的对象进行零初始化意味着:如果 T 是标量类型,则将对象设置为值 0(零),作为整数常量表达式,转换为 T" 。对于 POD,如果我没记错的话,这意味着 int i = int();i == 0 结尾
          • @wilhelmtell:那是你明确使用默认初始化的时候。在上述情况下,POD 类型未显式默认初始化,因此未定义值。这就是为什么我使用默认构造函数这个稍微模糊的术语。
          • 如果我没有定义一个移动构造函数,编译器是否也定义了一个?
          • @JorgeLuque 添加了移动部分。是的。 BUT 仅当您未定义复制操作或析构函数时。您可以使用Class(Class&amp;&amp;) = default; 强制创建默认版本。如果定义任一移动运算符,则必须同时定义两者。
          【解决方案8】:

          我认为值得指出的是,如果您不提供任何构造函数,编译器只会创建默认构造函数。这意味着如果您只提供一个带参数的构造函数,编译器将不会为您创建默认的无参数构造函数。

          您的书中谈到的归零行为可能特定于特定的编译器。我一直认为它可能会有所不同,并且您应该明确初始化任何数据成员。

          【讨论】:

            【解决方案9】:
            • 编译器会自动生成默认构造函数吗?
            • 隐式生成的默认构造函数是否执行零 初始化?

            如果您从法律角度分析 2003 标准的语言,那么答案是 yesno。然而,这不是全部,因为与用户定义的默认构造函数不同,在从头开始创建对象时并不总是使用隐式定义的默认构造函数 -- 这里是另外两个场景:无构造成员值初始化

            “无构造”的情况实际上只是一个技术问题,因为它在功能上与调用 trivial 默认构造函数没有什么不同。另一种情况更有趣:使用“()”调用成员值初始化[就像显式调用没有参数的构造函数一样]并且它绕过了技术上称为the 默认构造函数。相反,它递归地对每个数据成员执行值初始化,对于原始数据类型,这最终会解析为零初始化

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

                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
              
            • 如果你用“()”初始化一个对象,这不会直接调用默认构造函数。取而代之的是,它发起了一长串规则,称为值初始化 [8.5-7]

            • 值初始化的最终结果是永远不会调用隐式声明的默认构造函数。相反,会调用递归的成员值初始化,这将最终对任何原始成员进行零初始化,并在任何具有用户声明的构造函数的成员上调用默认构造函数 [8.5-5]

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

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

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

            那么这什么时候重要?

            • 可能存在泛型代码想要强制初始化未知类型的情况。值初始化提供了一种方法来做到这一点。请记住,如果用户提供了构造函数,则不会发生隐式零初始化。

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

                vector::resize( size_type sz, T c=T() ); // default c is "value-initialized"
              
            • 原始类型或“plain-old-data”(POD) 类型结构的整个数组可以使用值初始化语法进行零初始化。

                new int[100]();
              

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

            【讨论】:

            • 这是迄今为止最好的答案 - 不幸的是,我只能投票一次。所以其他人也应该投票!
            • 相关——结构的值初始化:stackoverflow.com/a/1069634/86967
            【解决方案10】:

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

            要使构造函数成为非平凡的构造函数,以下是任何一个都可以满足的条件:

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

            【讨论】:

              【解决方案11】:

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

              • 该类有一个引用字段
              • 或没有用户定义的默认构造函数的 const 字段
              • 或没有默认初始化程序的字段,带有已删除的默认构造函数

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-12-04
                • 1970-01-01
                • 1970-01-01
                • 2012-06-30
                • 2012-07-19
                相关资源
                最近更新 更多