琐碎的ctor(或dtor)与用户定义的空ctor(或dtor)之间有什么区别

时间:2016-06-09 13:16:28

标签: c++ constructor destructor

当一个类有一个简单的构造函数和/或一个普通的析构函数时,C ++标准定义了一些非常具体的行为。

例如,根据标准的§3.8/ 1:

  

类型T的对象的生命周期在以下时间结束:

     

- 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用开始,或

     

- 重用或释放对象占用的存储空间。

所以,

  • 如果一个对象不是可以破坏的,那么在调用析构函数之后访问该对象成员的任何尝试都是UB。
  • 如果一个对象是琐事可破坏的,那么在调用析构函数之后尝试访问该对象的成员是安全的而不是UB。

虽然这个例子可能不是最好的例子,但它表明行为的差异可能是至关重要的(UB /非UB),无论对象是否具有琐事可破坏性。

标准的第12.4 / 3条规定,如果隐含地定义,而非虚拟的,并且如果所有基类和班级T的成员是可以轻易破坏的。

在我的(谦虚)体验中,我从未在编译器生成的代码方面看到任何差异:

  • 一个具有普通默认ctor和/或普通dtor的课程,
  • 具有用户定义的空 ctor和/或非虚拟用户定义的空 dtor的类(只要该类,其基类和成员类也是将非虚拟dtor用户定义为空或平凡)

所以,我的问题是:

  • 用户定义的空ctor / dtor以何种方式可以或不可以被视为关于编译器代码生成,优化,权衡等的微不足道的ctor / dtor ......
  • 与用户定义的非空ctor / dtor相同的问题;什么规则应遵循ctor / dtor中实现的代码,将它们视为微不足道。

我的问题与标准无关(请不要回答标准说明什么是微不足道的ctor / dtor,因此用户定义的ctor / dtor不是),而是编译器处理用户定义的ctor / dtor的方式与琐碎的ctor / dtor相比,编译代码的行为可以改变(或不改变)的方式。

2 个答案:

答案 0 :(得分:1)

你比我更了解标准,但是根据你提供的信息,标准定义了一个简单的析构函数,但它没有定义一个空的析构函数,这会使这个问题有点误导。因此,一个简单的析构函数是编译器可以优化的特殊情况,虽然空构造函数对我们有意义,但编译器编写者不必考虑它。

浏览一些SO链接:

要回答你的第二个问题,一旦你的ctor非空,这不是微不足道的。你得到的最接近的是一个空的ctor / dtor,你仔细阅读标准已经告诉你,这并没有被定义为微不足道。

TL; DR:该标准定义了一个简单的dtor,但不是空的dtor。智能编译器可以选择注意它是用户定义的空白并将其视为微不足道,但标准不需要任何此类考虑。

答案 1 :(得分:1)

  

用户定义的空ctor / dtor以何种方式可以或不可以被视为关于编译器代码生成,优化,权衡等的平凡的ctor / dtor ...

如果没有内联构造函数/析构函数,那么编译器可能(取决于链接时优化)必须向它们发出调用,即使它们是无操作。

例如,以下代码:

struct Struct {
  Struct();
  ~Struct();
};

int main() {
  Struct s;
}

编译为(启用优化):

main:
        subq    $24, %rsp
        leaq    15(%rsp), %rdi
        call    Struct::Struct()
        leaq    15(%rsp), %rdi
        call    Struct::~Struct()
        xorl    %eax, %eax
        addq    $24, %rsp
        ret

请注意,仍然存在对构造函数和析构函数的调用,即使在单独的文件中我可以将它们定义为空函数。

但是,如果您已经定义了这些定义:

struct Struct {
  Struct() {}
  ~Struct() {}
};

Struct foo() {
  return Struct{};
}

然后编译器可以(并且如果它不完全吮吸)将它们视为琐碎的构造函数/析构函数:

foo():
        movq    %rdi, %rax
        ret

在该示例中,任何构造函数/析构函数调用都已完全优化,生成的代码与Struct的定义简单struct Struct {};相同。

  

与用户定义的非空ctor / dtor相同的问题;应该遵循ctor / dtor中实现的代码遵循哪些规则来将它们视为微不足道的。

这种取决于。同样,如果构造函数/析构函数没有内联,那么编译器可能仍然必须向它们发出调用,在这种情况下,它们并不是那么简单。

但是,如果优化器可以完全优化它们(例如,如果它们只包含for (int x = 0; x < 1000; ++x);,那么内联非空构造函数/析构函数可能仍然是“平凡的”,那么这是无用的代码,可以是优化了他们实际上是空的。

但如果他们做了有用的工作,而这些工作不能被优化掉,那么他们就不会像琐碎一样。他们会跑。他们必须。

相关问题