为什么C++编译器不从最终类优化此dynamic_cast?

Why don't C++ compilers optimize this dynamic_cast from a final class?

本文关键字:优化 dynamic cast 编译器 C++ 为什么      更新时间:2023-10-16

考虑这个类层次结构:

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

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

考虑以下两个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限定符),还是在这种情况下由于一些我仍然没有看到的微妙原因而无法进行优化?

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

我猜这有几个原因。 首先是dynamic_cast在唱片链上上下下投射。 当源记录具有最终属性时,您只需检查链是否 Dest 是祖先(因为不能有来自源的派生类)。

但是,如果课程不是最终课程怎么办?我怀疑可能还有更多。 也许多重继承使搜索向上的演员比向下的演员更困难? 在不停止调试器中的代码的情况下,我所能做的就是推测。

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


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