Должен ли я использовать dynamic_cast когда-либо для downcast? - PullRequest
5 голосов
/ 27 сентября 2019

Я заметил, что компилятор будет оптимизировать некоторые 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 для downcast, чтобы не было необходимости учитывать дополнительные издержки?

Ответы [ 2 ]

4 голосов
/ 27 сентября 2019

Основная проблема с 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

Как правило, трудно обеспечить конкретную методологию для динамических приведений, если это позволяет производительность, всегда динамическое приведение к понижению.В противном случае возможно неопределенное поведение и утечка безопасности.

Если вы не можете гарантировать, что производный класс относится к этому конкретному типу - у вас нет выбора, кроме как использовать динамическое приведение.

Если выесть гарантия, но в коде могут быть ошибки, рассмотрите возможность создания статического утверждения с динамическим приведением внутри и использования статического преобразования в оптимизированных версиях.

3 голосов
/ 27 сентября 2019

На самом деле, я не вижу никакой эффективной разницы в использовании static_cast и dynamic_cast здесь.Во-первых, если вы вызываете виртуальную функцию через приведенный указатель, все последующие вызовы будут иметь тот же эффект - они будут вызывать динамическую диспетчеризацию :

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

dynamic_cast может добавить некоторые накладные расходы, но наблюдаемый эффект будет таким же.А именно.Будет вызван B::f (если он переопределен).

Что касается не виртуальных функций из производного класса, вам необходимо привести:

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.

...