Поделиться через


Оператор dynamic_cast

Преобразует операнд expression в объект типа type-id.

Синтаксис

dynamic_cast < type-id > ( expression )

Замечания

Параметр type-id должен быть указателем или ссылкой на ранее определенный тип класса или "указателем на void". Тип операнда expression должен быть указателем, если type-id является указателем, или l-значением, если type-id является ссылкой.

Ознакомьтесь static_cast с объяснением разницы между статическими и динамическими преобразованиями приведения, а также при необходимости их использования.

В управляемом dynamic_cast коде есть два критических изменения:

  • dynamic_cast Указатель на базовый тип прямоугольной перечисления завершится ошибкой во время выполнения, возвращая значение 0 вместо преобразованного указателя.

  • dynamic_cast Больше не вызывает исключение, если type-id это внутренний указатель на тип значения. Вместо этого приведение завершается ошибкой во время выполнения. Приведение возвращает значение указателя 0 вместо того, чтобы вызывать.

Если type-id это указатель на однозначно доступный прямой или косвенный базовый класс expression, то указатель на уникальный подобъект типа type-id является результатом. Например:

// dynamic_cast_1.cpp
// compile with: /c
class B { };
class C : public B { };
class D : public C { };

void f(D* pd) {
   C* pc = dynamic_cast<C*>(pd);   // ok: C is a direct base class
                                   // pc points to C subobject of pd
   B* pb = dynamic_cast<B*>(pd);   // ok: B is an indirect base class
                                   // pb points to B subobject of pd
}

Этот тип преобразования называется "upcast", так как он перемещает указатель вверх по иерархии классов, от производного класса к классу, который он является производным. Восходящее приведение типа является неявным преобразованием.

Если type-id является указателем void*, выполняется проверка во время выполнения, чтобы определить фактический тип операнда expression. Результатом является указатель на полный объект, на который указывает операнд expression. Например:

// dynamic_cast_2.cpp
// compile with: /c /GR
class A {virtual void f();};
class B {virtual void f();};

void f() {
   A* pa = new A;
   B* pb = new B;
   void* pv = dynamic_cast<void*>(pa);
   // pv now points to an object of type A

   pv = dynamic_cast<void*>(pb);
   // pv now points to an object of type B
}

Если type-id это не void*так, выполняется проверка во время выполнения, чтобы узнать, можно ли преобразовать объект, на который указывает expression объект, в тип, на который указывает type-id.

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

// dynamic_cast_3.cpp
// compile with: /c /GR
class B {virtual void f();};
class D : public B {virtual void f();};

void f() {
   B* pb = new D;   // unclear but ok
   B* pb2 = new B;

   D* pd = dynamic_cast<D*>(pb);   // ok: pb actually points to a D
   D* pd2 = dynamic_cast<D*>(pb2);   // pb2 points to a B not a D
}

Этот тип преобразования называется "нисходящим приведением типа", поскольку при нем указатель перемещается вниз по иерархии классов: от заданного класса к производному от него классу.

В случаях множественного наследования возникают возможности для неоднозначности. Рассмотрим для примера иерархию классов, показанную на следующем рисунке.

Для типов СРЕДЫ CLR результаты либо no-op, dynamic_cast если преобразование может выполняться неявно, либо инструкция MSIL isinst , которая выполняет динамическую проверку и возвращает, если преобразование завершается nullptr ошибкой.

Следующий пример используется dynamic_cast для определения того, является ли класс экземпляром определенного типа:

// dynamic_cast_clr.cpp
// compile with: /clr
using namespace System;

void PrintObjectType( Object^o ) {
   if( dynamic_cast<String^>(o) )
      Console::WriteLine("Object is a String");
   else if( dynamic_cast<int^>(o) )
      Console::WriteLine("Object is an int");
}

int main() {
   Object^o1 = "hello";
   Object^o2 = 10;

   PrintObjectType(o1);
   PrintObjectType(o2);
}

Схема, показывающая несколько наследование.

На схеме показана иерархия классов с A в качестве базового класса B, который является базовым классом D. A также является базовым классом для C, который является базовым классом для D. Класс D наследует как от B, так и от C.

Указатель на объект типа D можно безопасно привести к B или C. Однако если в результате приведения D указывает на объект A, какой экземпляр объекта A будет являться результатом? Это может привести к ошибке неоднозначного приведения. Чтобы обойти эту проблему, можно выполнить два однозначных приведения. Например:

// dynamic_cast_4.cpp
// compile with: /c /GR
class A {virtual void f();};
class B : public A {virtual void f();};
class C : public A {virtual void f();};
class D : public B, public C {virtual void f();};

void f() {
   D* pd = new D;
   A* pa = dynamic_cast<A*>(pd);   // C4540, ambiguous cast fails at runtime
   B* pb = dynamic_cast<B*>(pd);   // first cast to B
   A* pa2 = dynamic_cast<A*>(pb);   // ok: unambiguous
}

При использовании виртуальных базовых классов могут возникать дополнительные неоднозначности. Рассмотрим для примера иерархию классов, показанную на следующем рисунке.

Схема иерархии классов, показывающих виртуальные базовые классы.

На схеме показаны классы A, B, C, D и E, упорядоченные следующим образом: Класс A является базовым классом B. КлассЫ C и E, производные от B. Класс E также наследуется от класса B, который наследует от класса A.

Иерархия классов, показывающая виртуальные базовые классы

В этой иерархии объект A является виртуальным базовым классом. Учитывая экземпляр класса E и указатель на A подобъект, dynamic_cast указатель на указатель B на ошибку из-за неоднозначности. Необходимо сначала выполнить обратное приведение к полному объекту E, затем однозначным образом вернуться вверх по иерархии, чтобы дойти до нужного объекта B.

Рассмотрим для примера иерархию классов, показанную на следующем рисунке.

Схема иерархии классов, показывающих повторяющиеся базовые классы.

На схеме показаны классы A, B, C, D и E, упорядоченные следующим образом: класс B является производным от класса A. Класс C является производным от класса A. Класс D является производным от класса B. Класс E является производным от класса C, который является производным от класса A. В этом случае повторяющийся базовый класс — это класс A, который прямо или косвенно наследуется всеми другими классами. Класс A наследуется непосредственно классами B и C, а также косвенно по классу D через класс B и косвенно по классу E через класс C и косвенно в классе D через класс B.

Иерархия классов, показывающая повторяющиеся базовые классы

При использовании заданного объекта типа E и указателя на подобъект D можно выполнить три преобразования, чтобы перейти от подобъекта D к крайнему слева подобъекту A. Вы можете выполнить dynamic_cast преобразование из D указателя E в указатель, а затем преобразование (либо dynamic_cast неявное преобразование) в E B, а затем неявное преобразование из B A. Например:

// dynamic_cast_5.cpp
// compile with: /c /GR
class A {virtual void f();};
class B : public A {virtual void f();};
class C : public A { };
class D {virtual void f();};
class E : public B, public C, public D {virtual void f();};

void f(D* pd) {
   E* pe = dynamic_cast<E*>(pd);
   B* pb = pe;   // upcast, implicit conversion
   A* pa = pb;   // upcast, implicit conversion
}

Оператор dynamic_cast также можно использовать для выполнения перекрестного приведения. Используя ту же иерархию классов, можно привести указатель, например, из B подобъекта в D подобъект, если полный объект имеет тип E.

Учитывая перекрестные приведения, можно выполнить преобразование с указателя на указатель D на A левый элемент подобъекта всего за два шага. Можно перекрестное приведение из D в B, а затем неявное преобразование из B в A. Например:

// dynamic_cast_6.cpp
// compile with: /c /GR
class A {virtual void f();};
class B : public A {virtual void f();};
class C : public A { };
class D {virtual void f();};
class E : public B, public C, public D {virtual void f();};

void f(D* pd) {
   B* pb = dynamic_cast<B*>(pd);   // cross cast
   A* pa = pb;   // upcast, implicit conversion
}

Значение указателя null преобразуется в значение null указателя целевого типа по dynamic_cast.

При использовании dynamic_cast < type-id > ( expression ), если expression не удается безопасно преобразовать в тип type-id, проверка во время выполнения приводит к сбою приведения. Например:

// dynamic_cast_7.cpp
// compile with: /c /GR
class A {virtual void f();};
class B {virtual void f();};

void f() {
   A* pa = new A;
   B* pb = dynamic_cast<B*>(pa);   // fails at runtime, not safe;
   // B not derived from A
}

Значением приведения к типу указателя, которое привело к сбою, является пустой указатель. Сбой приведения к типу ссылки вызывает исключение bad_cast. Если expression не указывает на допустимый объект или не ссылается на нее, __non_rtti_object создается исключение.

Сведения об исключении см. в описании __non_rtti_object типа.

Пример

В следующем примере создается указатель базового класса (структура A) на объект (структура C). Это (а также тот факт, что они являются виртуальными функциями) порождает полиморфизм времени выполнения.

В примере также вызывается невиртуальная функция в иерархии.

// dynamic_cast_8.cpp
// compile with: /GR /EHsc
#include <stdio.h>
#include <iostream>

struct A {
    virtual void test() {
        printf_s("in A\n");
   }
};

struct B : A {
    virtual void test() {
        printf_s("in B\n");
    }

    void test2() {
        printf_s("test2 in B\n");
    }
};

struct C : B {
    virtual void test() {
        printf_s("in C\n");
    }

    void test2() {
        printf_s("test2 in C\n");
    }
};

void Globaltest(A& a) {
    try {
        C &c = dynamic_cast<C&>(a);
        printf_s("in GlobalTest\n");
    }
    catch(std::bad_cast) {
        printf_s("Can't cast to C\n");
    }
}

int main() {
    A *pa = new C;
    A *pa2 = new B;

    pa->test();

    B * pb = dynamic_cast<B *>(pa);
    if (pb)
        pb->test2();

    C * pc = dynamic_cast<C *>(pa2);
    if (pc)
        pc->test2();

    C ConStack;
    Globaltest(ConStack);

   // fails because B knows nothing about C
    B BonStack;
    Globaltest(BonStack);
}
in C
test2 in B
in GlobalTest
Can't cast to C

См. также

Операторы приведения
Ключевые слова