为什么不让C ++编译器从最终类中优化这个dynamic_cast?

时间:2017-06-11 23:16:01

标签: c++ polymorphism final dynamic-cast

考虑这个类层次结构:

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

我的理解是将final放在class Dog上可以确保任何人都无法创建一个继承自Dog的类,这种推论意味着没有人可以创建一个类 - 同时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 __dynamic_cast的最佳代码(&#34;总是返回nullptr&#34;),但为to_final生成{ {1}}。

这真的是一个错过的优化机会(在编译器中显然某人投入了一些努力来尊重强制转换中的final限定符),或者在这种情况下对某些人来说是不可能的优化我还没有看到的微妙原因?

1 个答案:

答案 0 :(得分:3)

好的,我挖过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并不总是因为最终属性而总是为空,但另外因为Cat不在Dog派生的记录链中。当然,如果它没有final属性,那么它就会提前退出(就像Cat是Src时那样),但它不一定总是为空。

我猜这有几个原因。第一个是dynamic_cast在记录链中上下投射。当源记录具有最终属性时,如果Dest是祖先,则可以简单地检查链(因为源中没有派生类)。

但如果班级不是最终的怎么办?我怀疑可能会有更多。也许多重继承使搜索投射比投射更难?在不停止调试器中的代码的情况下,我所能做的就是推测。

我知道:isAlwaysNull是一个早期退出的功能。这是一个合理的断言,它试图证明结果总是为空。你无法证明消极(正如他们所说),但你可以反驳一个积极的。

也许您可以查看该文件的Git历史记录,并通过电子邮件发送编写该功能的人员。 (或者至少添加了final支票)。