【问题标题】:Why don't C++ compilers optimize this dynamic_cast from a final class?为什么 C++ 编译器不从最终类优化这个 dynamic_cast?
【发布时间】:2017-06-11 23:16:00
【问题描述】:

考虑这个类层次结构:

struct Animal { virtual ~Animal(); };
struct Cat : virtual Animal {};
struct Dog final : virtual Animal {};

我的理解是,将final 放在class Dog 上可以确保没有人可以创建一个继承自Dog 的类,这必然意味着没有人可以创建一个IS-A Dog 和IS -A Cat 同时。

考虑这两个dynamic_casts:

Dog *to_final(Cat *c) {
    return dynamic_cast<Dog*>(c);
}

Cat *from_final(Dog *d) {
    return dynamic_cast<Cat*>(d);
}

GCC、ICC 和 MSVC 忽略 final 限定符并生成对 __dynamic_cast 的调用;这很不幸,但并不奇怪。

令我惊讶的是,Clang 和 Zapcc 都是 generate 的最佳代码 from_final (“总是返回 nullptr”),但会为 to_final 生成一个对 __dynamic_cast 的调用。

这真的是一个错过的优化机会吗(在编译器中,显然 有人 付出了一些努力来尊重强制转换中的final 限定符),或者在这种情况下由于某些微妙的原因无法进行优化我还没看到?

【问题讨论】:

  • 我的猜测是,这种情况不会经常出现,以至于大多数编译器都不会过分担心。优化是为了响应现实世界对效率的共同需求而实施的。
  • @cdhowie:你可能是对的;但让我停下来的是,显然有人确实from_final 案例中编写了Clang 优化。 to_final 案例是对称的(特别是在 codegen 方面,它会为两种类型提取 typeinfo),但未知的人 not 添加了对称优化。 “明显的半实现优化”在我的大脑中似乎比“根本没有优化”更奇怪(参见 GCC、ICC、MSVC)。

标签: c++ polymorphism final dynamic-cast


【解决方案1】:

好的,我翻遍了 Clang 的source code to find this method

/// isAlwaysNull - Return whether the result of the dynamic_cast is proven
/// to always be null. For example:
///
/// struct A { };
/// struct B final : A { };
/// struct C { };
///
/// C *f(B* b) { return dynamic_cast<C*>(b); }
bool CXXDynamicCastExpr::isAlwaysNull() const
{
  QualType SrcType = getSubExpr()->getType();
  QualType DestType = getType();

  if (const PointerType *SrcPTy = SrcType->getAs<PointerType>()) {
    SrcType = SrcPTy->getPointeeType();
    DestType = DestType->castAs<PointerType>()->getPointeeType();
  }

  if (DestType->isVoidType()) // always allow cast to void*
    return false;

  const CXXRecordDecl *SrcRD = 
    cast<CXXRecordDecl>(SrcType->castAs<RecordType>()->getDecl());

  //********************************************************************
  if (!SrcRD->hasAttr<FinalAttr>()) // here we check for Final Attribute
    return false; // returns false for Cat
  //********************************************************************

  const CXXRecordDecl *DestRD = 
    cast<CXXRecordDecl>(DestType->castAs<RecordType>()->getDecl());

  return !DestRD->isDerivedFrom(SrcRD); // search ancestor types
}

我对解析代码有点厌倦了,但在我看来,您的 from_final 并不仅仅因为 Final 属性而始终为 null,而且还因为 Cat 不在Dog 派生记录链。当然,如果它没有 final 属性,那么它会提前退出(就像 Cat 是 Src 时那样),但它不一定总是为 null。

我猜这有几个原因。第一个是 dynamic_cast 向上和向下转换记录链。当 Source Record 具有 Final Attribute 时,您可以简单地检查链是否 Dest 是祖先(因为不能从 Source 派生类)。

但是如果类不是最终的呢?我怀疑可能还有更多。也许多重继承使得向上转换比向下转换更难搜索?无需在调试器中停止代码,我所能做的就是推测。

我知道:isAlwaysNull 是一个提前退出的函数。这是一个合理的断言,它试图证明结果始终为空。你不能证明消极(正如他们所说),但你可以反驳积极。


也许您可以检查该文件的 Git 历史记录并通过电子邮件发送给编写该函数的人。 (或者至少添加了final 检查)。

【讨论】:

  • 根据dynamic_cast docs,它可以向上、向下和向侧面投射。我怀疑Final 不仅会阻止未来的演员阵容,还会阻止横向演员。同样,我认为搜索继承记录可能会非常昂贵(至少对于这种微不足道的检查)。
  • 不错的挖掘! “第一个是 dynamic_cast 向上和向下转换记录链。当源记录具有最终属性时,您可以简单地检查链是否 Dest 是祖先” - 但请注意,如果 Dest 有Final 属性,则 Dest 不能是 anything 的祖先;在这种情况下,无需检查任何链条。
  • 昨晚有点累,一直在重写那行。无论如何,是的,你是对的。我不禁认为未进行检查的唯一原因是 1)搜索时间或 2)遗漏(正如您所指出的)。干杯!
猜你喜欢
  • 2015-05-13
  • 1970-01-01
  • 1970-01-01
  • 2018-11-14
  • 1970-01-01
  • 2015-12-27
  • 2011-04-17
  • 1970-01-01
  • 2017-07-25
相关资源
最近更新 更多