为什么C++编译器不对来自final类的dynamic_cast进行优化?

13
考虑下面的类层次结构:
struct Animal { virtual ~Animal(); };
struct Cat : virtual Animal {};
struct Dog final : virtual Animal {};

我的理解是在 class Dog 上加上 final 可以确保没有人可以创建一个继承自 Dog 的类,这也意味着没有人可以创建一个同时 IS-A Dog 和 IS-A Cat 的类。
考虑这两个 dynamic_cast:
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都会生成from_final(“始终返回nullptr”)的最佳代码,但对于to_final则会生成对__dynamic_cast的调用。

在这个编译器中,是否真的错过了优化机会(显然有人花费了一些精力来尊重转换中的final限定符),还是由于某些微妙的原因而在这种情况下无法进行优化,这仍然让我困惑?


4
我猜这种情况并不经常出现,大多数编译器不会过于担心它。针对常见的现实需求,优化通常是为了提高效率而实现的。 - cdhowie
@cdhowie:你可能是对的;但是让我犹豫的是,某个人明显确实费了心思在“from_final”情况下编写Clang优化。 “to_final”情况对称(尤其是在代码生成方面,它正在提取两种类型的类型信息),然而那个未知的人没有添加对称优化。“明显只有一半实现的优化”比“根本没有优化”更奇怪(参见GCC,ICC,MSVC)。 - Quuxplusone
1个回答

4

好的,我深入研究了Clang的源代码,找到了这个方法

/// 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特性而一直为空,而且还因为Cat不在Dog派生记录链中。当然,如果它没有final属性,那么它将会提前退出(就像Cat是Src时所做的那样),但它不一定总是空的。
我猜这可能有几个原因。首先,dynamic_cast可以向上和向下转换记录链。当源记录具有Final特性时,您可以简单地检查链是否是祖先(因为无法从源中派生类)。
但是如果该类不是final呢?我认为这可能还有更多的原因。也许多重继承使搜索向上转换比向下转换更加困难?只能凭借猜测,没有在调试器中停止代码。
这是我知道的:isAlwaysNull是一个早期退出函数。它合理地断言正在尝试证明结果始终为空。不能证明否定(正如他们所说的),但可以证明肯定。
也许您可以检查文件的Git历史记录,并向编写该函数的人发送电子邮件(或至少添加final检查)。

1
根据 dynamic_cast 文档,它可以进行向上、向下和侧向转换。我怀疑 Final 不仅防止未来的转换,也阻止了侧向转换。再次强调,我认为搜索继承记录可能会非常昂贵(至少对于这种琐碎的检查而言)。 - James Poag
不错的挖掘!“首先,dynamic_cast可以向上和向下转换记录链。当源记录具有最终属性时,您可以简单地检查链以确定Dest是否是祖先”——但请注意,如果Dest具有最终属性,则Dest不能是任何东西的祖先;在这种情况下,无需检查任何链。 - Quuxplusone
昨晚我有点累,一直在重写那行代码。不管怎样,是的,你说得对。我不禁想到没有进行检查的唯一原因是1)搜索时间或2)遗漏(正如你所指出的)。干杯! - James Poag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接