【问题标题】:Why would the implicitly generated constructor (et al.) be more efficient than a user-defined (trivial) one?为什么隐式生成的构造函数(等)比用户定义的(微不足道的)构造函数更有效?
【发布时间】:2010-11-25 10:08:34
【问题描述】:

我今天早上从 D. Kalev 那里读到了这个article 关于新的 c++11 特性“默认和删除函数”的内容,无法理解关于性能的部分,即:

特殊成员函数的手动定义(即使它是微不足道的)通常比隐式定义的效率低。

通过谷歌搜索找到答案,我找到了同一作者的另一个article

综合构造函数和复制构造函数使实现能够创建比用户编写的代码更高效的代码,因为它可以应用其他方式并不总是可能的优化。

没有解释,但我不时阅读类似的说法。

但那是怎么写的:

class C { C() = default; };

更有效率
class C { C(){} };

?我虽然编译器足够聪明,可以检测到这种情况并对其进行优化。换句话说,当编译器看到=default 而不是{}(void body 函数)时,如何更容易优化?

编辑:该问题已被编辑以添加“c++11”标签,但此问题仍保留在 c++03 上下文中:只需将 class C {C()=default;}; 替换为 class C {};,因此不是特定于 c++11问题。

【问题讨论】:

  • 好问题。我也认为任何不优化它的编译器都是有缺陷的。让我们看看是否有人可以给出一个很好的理由说明为什么这是不可能的。
  • 尝试使用 C++1x 编译器生成程序集。这肯定会回答你的问题。我怀疑有什么区别,因为正如你所说,编译器足够聪明。在我看来,像你引用的评论很糟糕,因为它让 C++ 开发人员过多地考虑性能。 99% 的情况下,在考虑愚蠢的微观性能优化之前,还有其他一些品质需要担心。
  • @Daniel:谢谢,但我对组装一无所知,而且我对获得微性能一点也不感兴趣......但我很想知道为什么 会有任何收获,无论是微观的。
  • 你确定它们相似吗,意思是,第二个是内联定义,但我不确定第一个。

标签: c++ performance c++11 constructor


【解决方案1】:

你问,这是怎么回事

class C { C() = default; };

更有效率
class C { C(){} };

好吧,两个构造函数都不做任何事情,所以对于那个例子来说谈论效率是没有意义的。

但更一般地说,在例如一个复制构造函数可以想象一次复制一个 POD 项目不会被简单的优化器识别为可优化的,而自动生成它可能只是做一个memcpy。谁知道。这是一个实施质量问题,我也可以轻松想象相反的

所以,衡量,如果它重要。

干杯,

【讨论】:

  • 我完全同意,但 Kalev 给出了几乎相同的例子(实际上是一个虚拟默认析构函数),他说(在帖子中引用):“即使它是微不足道的”,因此是我的问题。但我永远不会衡量,我想了解真正的原因,而不是作者给出的。
  • 这两种情况都不做,它们都使用默认构造函数初始化所有类的成员(如果它们不是原语)
  • @Motti:在上面的具体示例中,他们什么都不做。在这种情况下,谈论效率是没有意义的。在经过修改的示例中,具有数据成员的示例通常具有不同的效果。所以在这种情况下,虽然效率可以谈,但没有可比性。我认为讨论那个假设的案例没有任何意义。干杯&hth.,
【解决方案2】:

谈论“特殊成员函数的手动定义(即使它是微不足道的)”毫无意义,因为根据定义,用户提供的特殊成员函数是不平凡的。当使用类型特征和 POD 特性时,这种非平凡性就会发挥作用,并且许多优化只能使用平凡或 POD 类型。

对同一引言的更好的重述是:

默认的特殊成员函数使能够检测到对这些函数的调用可能会被完全忽略。

来自第 12.1 节 [class.ctor]

如果默认构造函数是平凡 既不是用户提供也不是删除 如果:

  • 它的类没有虚拟 功能(10.3)并且没有虚拟基础 类 (10.1) 和
  • 没有非静态 其类的数据成员有一个 大括号或相等初始化器,以及
  • 全部 其类的直接基类 有简单的默认构造函数,并且
  • 对于所有非静态数据成员 属于类类型的类 (或其数组),每个这样的类 有一个简单的默认构造函数。

否则,默认构造函数是 非平凡

来自第 12.8 节[class.copy]

类 X 的复制/移动构造函数是 微不足道 如果它既不是用户提供的 也没有删除,如果

  • X 类没有 虚函数(10.3)并且没有 虚拟基类 (10.1),以及
  • 选择复制/移动的构造函数 每个直接基类子对象是 微不足道的,和
  • 对于每个非静态 X 属于类类型的数据成员 (或其数组),构造函数 选择复制/移动该成员是 微不足道的;

否则复制/移动 构造函数是非平凡的

来自第 9 节,[class]

一个可简单复制类是一个类 那个:

  • 没有重要的副本 构造函数(12.8),
  • 没有 非平凡的移动构造函数 (12.8),
  • 没有重要的副本分配 运算符(13.5.3、12.8),
  • 没有 非平凡的移动赋值运算符 (13.5.3, 12.8) 和
  • 有一个琐碎的 析构函数 (12.4)。

一个平凡的类是一个具有 平凡的默认构造函数(12.1)和 可以轻松复制。 [注:在 特别是,可简单复制的或 平凡的类没有虚拟 函数或虚拟基类。 — 尾注]

【讨论】:

    【解决方案3】:

    对性能声明“持保留态度”。

    我听说一位 MIT 高分教授对他最喜欢的东西做出这样的声明,而没有人问他“为什么”的唯一原因是因为他是一位高分 MIT 教授。

    这样的构造函数和析构函数可能还有其他优点,但是关于性能的声明(除了大 O 之外)很少有意义,除非在高度人为的情况下。

    【讨论】:

      【解决方案4】:

      说实话,我也看不到。

      除此之外,我明白为什么应该使用

      class C { C() = default; };
      

      在我看来是一样的

      class C { };
      

      或者,如果提供了其他构造函数,如:

      class C { 
          C() {}
          // other constructors.
      };
      

      我没有看到作者在这里写的真正的问题

      【讨论】:

      • 我很喜欢 Daniel Lidström 在此处问题下的评论。我认为它指出了作者所写的不真实的问题。
      • 前两个是相同的,默认构造函数是默认的,显式或隐式没有区别。 “如果提供了其他构造函数”正是新的= default 语法的原因,因为它允许创建具有用户定义的转换的普通可复制 类。具有空主体的构造函数足以使类不可复制
      • @Ben 好的,但在我看来,这篇文章的重点是编译器能够使用= default 语法输出更优化的二进制文件。
      • 我建议您阅读我的答案以获得更详细的外观。这篇文章有一个问题,因为作者没有使用 trivial 的标准定义(我不是指普遍接受的定义,我的意思是 ISO C++ 标准明确规定了 trivial 表示在 C++ 类、构造函数和析构函数的上下文中)。结果是默认构造函数和空的用户定义构造函数之间的区别对编译器并不重要,但对于使用类型特征的库代码而言。对于某些图书馆来说,这确实是一个很大的不同。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-02-28
      • 2014-12-02
      • 2016-12-28
      • 1970-01-01
      • 1970-01-01
      • 2012-11-09
      • 1970-01-01
      相关资源
      最近更新 更多