我是否应该在沮丧时使用 dynamic_cast?

Should I use dynamic_cast whenever for downcast?

我注意到当以下操作是非多态时,编译器会优化掉一些 dynamic_cast,例如以下代码:


#include <iostream>

using namespace std;

struct A
{
    virtual ~A() = default;
    virtual int f()
    {
        return 1;
    };
    virtual int g() = 0;
};

struct B : A
{
    int g() const
    {
        return 2;
    }
    int g() override
    {
        return 3;
    }
    int h()
    {
        return 4;
    }
};

int main()
{
    A*   p  = new B();
    auto u  = p->f();
    auto v1 = static_cast<B*>(p)->f();
    auto v2 = dynamic_cast<B*>(p)->f();
    auto w  = p->g();
    auto x1 = static_cast<const B*>(p)->g();
    auto x2 = dynamic_cast<B*>(p)->g();
    auto x3 = dynamic_cast<const B*>(p)->g();
    auto y  = dynamic_cast<B*>(p)->h();
    cout << u << v1 << v2 << w << x1 << x2 << x3 << y << endl;
    delete p;
    return 0;
}

只有两个 dynamic_cast 调用是用 g++ -O2 编译的,这意味着等于 static_cast,所以我应该总是使用 dynamic_cast 进行向下转换,这样就不会产生额外的开销考虑过?

dynamic_cast 的主要问题是它们非常缓慢且复杂。当编译器知道编译时的实际类型时,他们确实可以优化它,但并非总是如此。从技术上讲,如果编译器知道如何正确执行,您的代码应该有 0 个动态转换。因此,不要过分相信编译器使用的确切优化机制。

您的代码的某些部分可以通过滥用未定义的行为进行优化。例如:

dynamic_cast<B*>(p)->f(); 
// this is optimized instantly to p->f(); 
// if dynamic_cast returns nullptr it would be undefined behavior IIRC, 
// so optimizer can assume that the cast is successful and it becomes equivalent to
// static_cast<B*>(p)->f() after optimization,
// regardless of whether p is actually of type B or not

一般来说,很难提供动态转换的具体方法,如果性能允许,总是动态转换向下转换。不这样做可能会导致未定义的行为和安全漏洞。

如果您不能保证派生的 class 是这种特定类型 - 您别无选择,只能使用动态转换。

如果你有保证但代码中可能存在错误,请考虑在内部进行动态转换的静态断言,并在优化版本中使用静态转换。

事实上,我看不出这里使用static_castdynamic_cast有什么有效区别。首先,如果您通过转换指针调用 虚函数,则以下所有调用将产生完全相同的效果——它们将触发 动态调度

auto v0 = p->f();
auto v1 = static_cast<B*>(p)->f();
auto v2 = dynamic_cast<B*>(p)->f();

dynamic_cast 可能会增加一些开销,但可观察到的效果是一样的。即。 B::f 将被调用(如果它被覆盖)。

至于派生的class的非虚函数,你需要转换:

auto x1 = static_cast<const B*>(p)->g();
auto x3 = dynamic_cast<const B*>(p)->g();

如果这些转换无效,您将在这两种情况下得到未定义的行为。如果它们有效,则实际上是相同的。再一次,有一些额外的开销 dynamic_cast.

请注意,IMO,您的程序中没有 dynamic_cast 优化。您可以在程序集中观察到两个 dynamic_cast 调用,并且您的代码中有两种不同形式的 dynamic_cast。我假设编译器只执行一次 dynamic_cast<B*> 并使用结果 3 次。


请注意,您可以通过以下语法手动选择要调用的 f 来绕过动态调度:

auto y1 = static_cast<B*>(p)->A::f();
auto y2 = static_cast<B*>(p)->B::f();

或者,与 dynamic_cast 完全相同。

现场演示:https://wandbox.org/permlink/CZRLPWxHjSMk8dFK.