【问题标题】:Are empty constructors always called in C++?在 C++ 中是否总是调用空构造函数?
【发布时间】:2011-02-23 21:43:01
【问题描述】:

我有一个一般性问题,可能与编译器有关。 我对调用构造函数的条件感兴趣。具体来说,在发布模式/针对速度优化的构建中,当您实例化一个对象时,是否总是会调用编译器生成的或空的构造函数?

class NoConstructor  
{  
    int member;  
};  

class EmptyConstructor  
{  
    int member;  
};

class InitConstructor  
{  
    InitConstructor()  
        : member(3)   
    {}  
    int member;  
};

int main(int argc, _TCHAR* argv[])  
{  
    NoConstructor* nc = new NoConstructor(); //will this call the generated constructor?  
    EmptyConstructor* ec = new EmptyConstructor(); //will this call the empty constructor?  
    InitConstructor* ic = new InitConstructor(); //this will call the defined constructor  

    EmptyConstructor* ecArray = new EmptyConstructor[100]; //is this any different?
}

我做了很多搜索,并花了一些时间在 Visual Studio 中查看生成的汇编代码。不过,在发布版本中可能很难遵循。

总结: 构造函数总是被调用吗?如果有,为什么?

我知道这在很大程度上取决于编译器,但肯定有一个共同的立场。您可以引用的任何示例/来源将不胜感激。

【问题讨论】:

  • 如果您不介意我的提问,这只是出于好奇,还是您的成品质量取决于编译器是否对此进行了优化?
  • 两者都有。我正在编写一个数组类(作为练习),它使用类似于 boost::is_pod​​ 的东西来决定是否将模板特化与使用 memswap/memcopy 的实现一起使用,而不是显式调用构造函数/析构函数/赋值运算符。这让我想知道这实际上会对 pod 类型的性能产生多大影响。
  • 我希望你不是在你的真实程序中那样动态分配。

标签: c++ constructor compiler-optimization default-constructor


【解决方案1】:

在实例化对象时是否总是调用编译器生成的构造函数/空构造函数?

没有。如果您的类是所谓的“POD”(普通旧数据),那么编译器生成的构造函数不会总是被调用。

具体如下两种情况不会调用:

struct Pod {
    int x;
};

int main() {
    Pod pod;
    std::cout << pos.x << std::endl; // Value undefined.

    Pod pod2 = Pod(); // Explicit value initialization.


    Pod* pods = new Pod[10];
    // Values of `pods` undefined.

    Pod* pods2 = new Pod[10](); // Explicit value initialization.
}

类型何时是 POD 的条件有点棘手。 C++ FAQ has a nice breakdown

【讨论】:

  • 这是一个很好的答案。谢谢康拉德:)。我的后续问题是 - 如果一个对象没有显式(用户定义的)构造函数,为什么编译器不总是优化它(假设该类不包含任何定义构造函数的成员对象)?我是不是对 pod 类型给出了一个模糊的定义?
  • Pod* pods2 = new Pod[10](); // Explicit constructor call. 此评论不正确,没有构造函数调用。 () 调用值初始化而不是构造函数调用。隐式定义的默认构造函数会使成员 x 未初始化。同样在Pod pod2 = Pod(); 中,虽然确实有构造函数调用,但只是调用了复制构造函数。
  • @KonradRudolph:严格来说,因为它缺少用户声明的默认构造函数。它的 POD 特性与值初始化无关。
  • 但是:Explicit value initialization 基本上相当于调用构造函数的 POD。我虽然是零初始化。但是没有方便阅读的标准。
  • @MartinYork:不,值初始化仅区分具有或不具有用户声明的构造函数的类。 POD-ness 对于值初始化并不重要;这对于默认初始化很重要。
【解决方案2】:

从逻辑上讲,构造函数被调用。在生成的代码中,如果构造函数什么都不做,就没有可以追溯到构造函数的指令,除非你的编译器在优化方面非常糟糕并且插入了一个对刚刚返回的东西的调用。

【讨论】:

  • 我怀疑这一点,但我无法证明/验证它。您是否有任何参考资料来支持这一点,或者可能有一些反汇编的例子?
  • 任何证明都取决于编译器和标志,而不是真正的证明,因为您无法通过测试涵盖所有可能的情况。你使用哪个编译器?
  • 目前为 MSVC9。关于如何测试这种行为的任何提示都会很棒!
  • 从 VC9 的混合源/反汇编窗口中可以清楚地看到,没有调用空 ctors...不知道如何演示它。
  • @Erik:当没有用户声明的默认构造函数的类类型的对象被值初始化时,构造函数不会被调用(甚至在逻辑上),这适用于问题中的一半情况。
【解决方案3】:

在优化模式下,如果你的类或结构是 POD(只有 POD 类型)并且没有指定构造函数,任何生产质量的 C++ 编译器不仅会跳过对构造函数的调用,甚至不会生成它。

如果您的类有非 POD 成员必须调用其构造函数,编译器将生成调用成员构造函数的默认构造函数。但即便如此 - 它也不会初始化 POD 类型。 IE。如果你不显式初始化member,你可能会在那里得到垃圾。

如果您的编译器/链接器具有 LTO,那么整个事情甚至会变得更精彩。

希望对您有所帮助!并首先让你的程序工作,然后使用分析器来检测慢的地方,然后优化它。过早的优化不仅可能使您的代码不可读并浪费您大量的时间,而且根本无济于事。您必须首先知道要优化什么。

这是您示例中代码的反汇编(x86_64,gcc 4.4.5):

main:
    subq    $8, %rsp
    movl    $4, %edi
    call    _Znwm
    movl    $4, %edi
    movl    $0, (%rax)
    call    _Znwm
    movl    $4, %edi
    movl    $0, (%rax)
    call    _Znwm
    movl    $400, %edi
    movl    $3, (%rax)
    call    _Znam
    xorl    %eax, %eax
    addq    $8, %rsp
    ret

如您所见,根本没有调用构造函数。根本没有类,每个对象只是一个 4 字节的整数。

使用 MS 编译器,YMMV。所以你必须自己检查拆卸。但结果应该是相似的。

祝你好运!

【讨论】:

  • 很棒的答案。谢谢!我实际上花了很多时间检查反汇编。我实际上并不满意生成的程序集(类似于您上面列出的程序)证明构造函数不会被调用。如果我从其他类中分配 100 个这些对象会怎样?当然,编译器不会简单地尝试将数据压入堆栈。这就是事情变得棘手的地方,我想我会在这里问...... :)
  • @JBeFat:在这种情况下,编译器将对齐数据(除非打开打包)并分配适当大小的块。但是构造函数仍然不会被调用。
【解决方案4】:

某些类或结构类型在 C++ 中称为 POD“Plain Old Data”。这些类型不会调用构造函数。

作为 POD 的规则非常重要,您应该查找它们,但总而言之:只包含原始数据类型,没有定义的构造函数。

【讨论】:

    【解决方案5】:

    您的示例不是很好,您在示例类中错过了public 关键字,而且在您的示例中,通过编写CLASS * class = new CLASS(); 而不是CLASS * class = new CLASS; 强制进行零初始化。

    在您所说的代码中 - 将始终按照标准要求执行零初始化。你可以随心所欲地调用它——但是会有代码来保证标准的规则。

    如果您在没有显示这个有争议的示例代码的情况下提问 - 那么唯一正确的答案将是 - compiler specific

    【讨论】:

    • 代码是在浏览器窗口中输入的。它可能无法编译,但它明白了这一点。这个问题是编译器特定的,但它似乎是跨编译器相当标准的行为。零初始化是什么意思?这不是一个真正有用的答案,但感谢您抽出宝贵时间回复:)
    • 正如我所写的那样 - 出现了许多更好的回复 :) 我的意思是 new DUMMYnew DUMMY() 是不同的东西。最后一个强制初始化 - 一种经常用于对 POD 进行零初始化的技巧。
    • 顺便说一句,好问题。我们可以期待很多关于“构造函数调用”的讨论)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-17
    • 2017-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多