dynamic_cast e static_cast em C ++

155

Estou bastante confuso com a dynamic_castpalavra - chave em C ++.

struct A {
    virtual void f() { }
};
struct B : public A { };
struct C { };

void f () {
    A a;
    B b;

    A* ap = &b;
    B* b1 = dynamic_cast<B*> (&a);  // NULL, because 'a' is not a 'B'
    B* b2 = dynamic_cast<B*> (ap);  // 'b'
    C* c = dynamic_cast<C*> (ap);   // NULL.

    A& ar = dynamic_cast<A&> (*ap); // Ok.
    B& br = dynamic_cast<B&> (*ap); // Ok.
    C& cr = dynamic_cast<C&> (*ap); // std::bad_cast
}

a definição diz:

A dynamic_castpalavra-chave lança um dado de um ponteiro ou tipo de referência para outro, executando uma verificação de tempo de execução para garantir a validade do elenco

Podemos escrever um equivalente de dynamic_castC ++ em C para que eu possa entender melhor as coisas?

Vijay
fonte
1
Se você quiser ter uma boa idéia de como dynamic_cast<>funciona nos bastidores (ou quanto do C ++ funciona), um bom livro (que também é bastante fácil de ler para algo tão técnico) é o "Inside the C ++ Object Model" de Lippman. Também os livros "Design and Evolution of C ++" e "The C ++ Programming Language" de Stroustrup são bons recursos, mas o livro de Lippman é dedicado a como o C ++ funciona 'nos bastidores'.
Michael Burr
O que significa o comentário na linha B* b2 = dynamic_cast<B*> (ap) // 'b'? b2 is pointer to bou o que?
LRDPRDX
@BogdanSikach Que pergunta é essa? Significa simplesmente que a AP é agora um tipo de classe B

Respostas:

282

Aqui está um resumo static_cast<>e, dynamic_cast<>especificamente, no que diz respeito aos ponteiros. Este é apenas um resumo de nível 101, que não cobre todos os meandros.

static_cast <Tipo *> (ptr)

Isso pega o ponteiro ptre tenta convertê-lo com segurança em um ponteiro do tipo Type*. Este elenco é feito em tempo de compilação. Ele só executará a conversão se os tipos de tipo estiverem relacionados. Se os tipos não estiverem relacionados, você receberá um erro do compilador. Por exemplo:

class B {};
class D : public B {};
class X {};

int main()
{
  D* d = new D;
  B* b = static_cast<B*>(d); // this works
  X* x = static_cast<X*>(d); // ERROR - Won't compile
  return 0;
}

dynamic_cast <Tipo *> (ptr)

Isso novamente tenta colocar o ponteiro ptre convertê-lo com segurança em um ponteiro do tipo Type*. Mas esse elenco é executado em tempo de execução, não em tempo de compilação. Por ser uma conversão em tempo de execução, é útil principalmente quando combinada com classes polimórficas. De fato, em casos certianos, as classes devem ser polimórficas para que o elenco seja legal.

As transmissões podem seguir uma de duas direções: da base para a derivada (B2D) ou da derivada para a base (D2B). É simples o suficiente para ver como o D2B lança funcionaria em tempo de execução. Ou ptrfoi derivado Typeou não era. No caso do D2B dynamic_cast <> s, as regras são simples. Você pode tentar converter qualquer coisa para qualquer outra coisa e, se de ptrfato for derivado Type, receberá um Type*ponteiro de volta dynamic_cast. Caso contrário, você receberá um ponteiro NULL.

Mas os modelos B2D são um pouco mais complicados. Considere o seguinte código:

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void DoIt() = 0;    // pure virtual
    virtual ~Base() {};
};

class Foo : public Base
{
public:
    virtual void DoIt() { cout << "Foo"; }; 
    void FooIt() { cout << "Fooing It..."; }
};

class Bar : public Base
{
public :
    virtual void DoIt() { cout << "Bar"; }
    void BarIt() { cout << "baring It..."; }
};

Base* CreateRandom()
{
    if( (rand()%2) == 0 )
        return new Foo;
    else
        return new Bar;
}


int main()
{
    for( int n = 0; n < 10; ++n )
    {
        Base* base = CreateRandom();

            base->DoIt();

        Bar* bar = (Bar*)base;
        bar->BarIt();
    }
  return 0;
}

main()não posso dizer que tipo de objeto CreateRandom()retornará, então o elenco no estilo C Bar* bar = (Bar*)base;não é, com certeza, seguro. Como você pôde consertar isso? Uma maneira seria adicionar uma função como bool AreYouABar() const = 0;à classe base e retornar truede Bare falsepara Foo. Mas há outra maneira: use dynamic_cast<>:

int main()
{
    for( int n = 0; n < 10; ++n )
    {
        Base* base = CreateRandom();

        base->DoIt();

        Bar* bar = dynamic_cast<Bar*>(base);
        Foo* foo = dynamic_cast<Foo*>(base);
        if( bar )
            bar->BarIt();
        if( foo )
            foo->FooIt();
    }
  return 0;

}

Os lançamentos são executados em tempo de execução e funcionam consultando o objeto (não há necessidade de se preocupar com o por enquanto), perguntando se é do tipo que estamos procurando. Se for, dynamic_cast<Type*>retorna um ponteiro; caso contrário, ele retornará NULL.

Para que essa conversão de base para derivada funcione dynamic_cast<>, Base, Foo e Bar devem ser o que o Padrão chama de tipos polimórficos . Para ser do tipo polimórfico, sua classe deve ter pelo menos uma virtualfunção. Se suas classes não são do tipo polimórfico, o uso de base para derivado dynamic_castnão será compilado. Exemplo:

class Base {};
class Der : public Base {};


int main()
{
    Base* base = new Der;
    Der* der = dynamic_cast<Der*>(base); // ERROR - Won't compile

    return 0;
}

A adição de uma função virtual à base, como um dtor virtual, criará os tipos polimórficos Base e Der:

class Base 
{
public:
    virtual ~Base(){};
};
class Der : public Base {};


int main()
{
    Base* base = new Der;
    Der* der = dynamic_cast<Der*>(base); // OK

    return 0;
}
John Dibling
fonte
9
Por que o compilador reclama disso em primeiro lugar? e não quando fornecemos apenas um dctor virtual apenas para base?
Rika
5
Note-se que se você fizer Base* base = new Base;, dynamic_cast<Foo*>(base)será NULL.
Yay295
2
@ Coderx7 dynamic_cast precisa de Informações sobre o Tipo de Tempo de Execução (RTTI), disponível apenas para classes polimórficas, ou seja, classes com pelo menos um método virtual.
Elvorfirilmathredia
@ Yay295 Por que dynamic_cast<Foo*>(base)nulo no caso de a Base* base = new Base;?
MuneshSingh
3
@ munesh Porque basenão é um Foo. Um Baseponteiro pode apontar para a Foo, mas ainda é uma Foo, portanto uma conversão dinâmica funcionará. Se você faz Base* base = new Base, baseé a Base, não é Foo, então você não pode convertê-lo dinamicamente para a Foo.
Yay295
20

A menos que você esteja implementando seu próprio RTTI manual (e ignorando o sistema), não é possível implementar dynamic_castdiretamente no código em nível de usuário do C ++. dynamic_castestá muito ligado ao sistema RTTI da implementação C ++.

Mas, para ajudá-lo a entender mais o RTTI (e, portanto dynamic_cast), você deve ler o <typeinfo>cabeçalho e o typeidoperador. Isso retorna as informações de tipo correspondentes ao objeto que você tem em mãos e você pode consultar várias coisas (limitadas) desses objetos de informações de tipo.

Chris Jester-Young
fonte
Eu apontaria para a Wikipedia, mas seus artigos sobre RTTI e dynamic_castsão muito reduzidos. :-P Apenas brinque com você mesmo até pegar o jeito. :-)
Chris Jester-Young
10

Mais do que código em C, acho que uma definição em inglês pode ser suficiente:

Dada uma classe Base da qual existe uma classe derivada Derivada, dynamic_castele converterá um ponteiro Base em um ponteiro Derivado se e somente se o objeto real apontado for de fato um objeto Derivado.

class Base { virtual ~Base() {} };
class Derived : public Base {};
class Derived2 : public Base {};
class ReDerived : public Derived {};

void test( Base & base )
{
   dynamic_cast<Derived&>(base);
}

int main() {
   Base b;
   Derived d;
   Derived2 d2;
   ReDerived rd;

   test( b );   // throw: b is not a Derived object
   test( d );   // ok
   test( d2 );  // throw: d2 is not a Derived object
   test( rd );  // ok: rd is a ReDerived, and thus a derived object
}

No exemplo, a chamada para testvincula objetos diferentes a uma referência Base. Internamente, a referência é reduzida para uma referência Derivedtipicamente segura: o downcast será bem-sucedido apenas nos casos em que o objeto referenciado for realmente uma instância de Derived.

David Rodríguez - dribeas
fonte
2
Eu acho que é melhor esclarecer que os exemplos compartilhados acima funcionarão com base em suposições se as classes forem apenas polimórficas, isto é, pelo menos, a classe Base possui pelo menos um método virtual.
irsis
1
Isso falhará porque classes não são tipos polimórficos.
username_4567
4

O seguinte não é muito parecido com o que você obtém dos C ++ dynamic_castem termos de verificação de tipo, mas talvez isso o ajude a entender um pouco melhor sua finalidade:

struct Animal // Would be a base class in C++
{
    enum Type { Dog, Cat };
    Type type;
};

Animal * make_dog()
{
   Animal * dog = new Animal;
   dog->type = Animal::Dog;
   return dog;
}
Animal * make_cat()
{
   Animal * cat = new Animal;
   cat->type = Animal::Cat;
   return cat;
}

Animal * dyn_cast(AnimalType type, Animal * animal)
{
    if(animal->type == type)
        return animal;
    return 0;
}

void bark(Animal * dog)
{
    assert(dog->type == Animal::Dog);

    // make "dog" bark
}

int main()
{
    Animal * animal;
    if(rand() % 2)
        animal = make_dog();
    else
        animal = make_cat();

    // At this point we have no idea what kind of animal we have
    // so we use dyn_cast to see if it's a dog

    if(dyn_cast(Animal::Dog, animal))
    {
        bark(animal); // we are sure the call is safe
    }

    delete animal;
}
Manuel
fonte
3

A dynamic_castexecuta uma verificação de tipo usando RTTI . Se falhar, gerará uma exceção (se você tiver dado uma referência) ou NULL se você tiver dado um ponteiro.

f4
fonte
2

Primeiro, para descrever a conversão dinâmica em termos C, temos que representar classes em C. Classes com funções virtuais usam um "VTABLE" de ponteiros para as funções virtuais. Comentários são C ++. Sinta-se à vontade para reformatar e corrigir erros de compilação ...

// class A { public: int data; virtual int GetData(){return data;} };
typedef struct A { void**vtable; int data;} A;
int AGetData(A*this){ return this->data; }
void * Avtable[] = { (void*)AGetData };
A * newA() { A*res = malloc(sizeof(A)); res->vtable = Avtable; return res; }

// class B : public class A { public: int moredata; virtual int GetData(){return data+1;} }
typedef struct B { void**vtable; int data; int moredata; } B;
int BGetData(B*this){ return this->data + 1; }
void * Bvtable[] = { (void*)BGetData };
B * newB() { B*res = malloc(sizeof(B)); res->vtable = Bvtable; return res; }

// int temp = ptr->GetData();
int temp = ((int(*)())ptr->vtable[0])();

Então, um elenco dinâmico é algo como:

// A * ptr = new B();
A * ptr = (A*) newB();
// B * aB = dynamic_cast<B>(ptr);
B * aB = ( ptr->vtable == Bvtable ? (B*) aB : (B*) 0 );
David Rayna
fonte
1
A pergunta inicial era "Podemos escrever um equivalente de dynamic_cast de C ++ em C".
David Rayna
1

Não há aulas em C, por isso é impossível escrever dynamic_cast nesse idioma. As estruturas C não têm métodos (como resultado, eles não têm métodos virtuais); portanto, não há nada "dinâmico" nela.

a1ex07
fonte
1

Não, não é fácil. O compilador atribui uma identidade exclusiva a todas as classes, essas informações são referenciadas por todas as instâncias de objetos e é isso que é inspecionado no tempo de execução para determinar se uma conversão dinâmica é legal. Você pode criar uma classe base padrão com essas informações e operadores para fazer a inspeção em tempo de execução nessa classe base; qualquer classe derivada informará a classe base sobre seu lugar na hierarquia de classes e quaisquer instâncias dessas classes poderão ser executadas em tempo de execução via suas operações.

editar

Aqui está uma implementação que demonstra uma técnica. Não estou afirmando que o compilador usa algo assim, mas acho que demonstra os conceitos:

class SafeCastableBase
{
public:
    typedef long TypeID;
    static TypeID s_nextTypeID;
    static TypeID GetNextTypeID()
    {
        return s_nextTypeID++;
    }
    static TypeID GetTypeID()
    {
        return 0;
    }
    virtual bool CanCastTo(TypeID id)
    {
        if (GetTypeID() != id) { return false; }
        return true;
    }
    template <class Target>
    static Target *SafeCast(SafeCastableBase *pSource)
    {
        if (pSource->CanCastTo(Target::GetTypeID()))
        {
            return (Target*)pSource;
        }
        return NULL;
    }
};
SafeCastableBase::TypeID SafeCastableBase::s_nextTypeID = 1;

class TypeIDInitializer
{
public:
    TypeIDInitializer(SafeCastableBase::TypeID *pTypeID)
    {
        *pTypeID = SafeCastableBase::GetNextTypeID();
    }
};

class ChildCastable : public SafeCastableBase
{
public:
    static TypeID s_typeID;
    static TypeID GetTypeID()
    {
        return s_typeID;
    }
    virtual bool CanCastTo(TypeID id)
    {
        if (GetTypeID() != id) { return SafeCastableBase::CanCastTo(id); }
        return true;
    }
};
SafeCastableBase::TypeID ChildCastable::s_typeID;

TypeIDInitializer ChildCastableInitializer(&ChildCastable::s_typeID);

class PeerChildCastable : public SafeCastableBase
{
public:
    static TypeID s_typeID;
    static TypeID GetTypeID()
    {
        return s_typeID;
    }
    virtual bool CanCastTo(TypeID id)
    {
        if (GetTypeID() != id) { return SafeCastableBase::CanCastTo(id); }
        return true;
    }
};
SafeCastableBase::TypeID PeerChildCastable::s_typeID;

TypeIDInitializer PeerChildCastableInitializer(&PeerChildCastable::s_typeID);

int _tmain(int argc, _TCHAR* argv[])
{
    ChildCastable *pChild = new ChildCastable();
    SafeCastableBase *pBase = new SafeCastableBase();
    PeerChildCastable *pPeerChild = new PeerChildCastable();
    ChildCastable *pSameChild = SafeCastableBase::SafeCast<ChildCastable>(pChild);
    SafeCastableBase *pBaseToChild = SafeCastableBase::SafeCast<SafeCastableBase>(pChild);
    ChildCastable *pNullDownCast = SafeCastableBase::SafeCast<ChildCastable>(pBase);
    SafeCastableBase *pBaseToPeerChild = SafeCastableBase::SafeCast<SafeCastableBase>(pPeerChild);
    ChildCastable *pNullCrossCast = SafeCastableBase::SafeCast<ChildCastable>(pPeerChild);
    return 0;
}
David Gladfelter
fonte
0

static_cast< Type* >(ptr)

static_cast em C ++ pode ser usado em cenários em que todos os tipos de conversão podem ser verificados em tempo de compilação .

dynamic_cast< Type* >(ptr)

dynamic_cast em C ++ pode ser usado para executar conversão de tipo segura para baixo . dynamic_cast é um polimorfismo em tempo de execução. O operador dynamic_cast, que converte com segurança de um ponteiro (ou referência) em um tipo base em um ponteiro (ou referência) em um tipo derivado.

por exemplo 1:

#include <iostream>
using namespace std;

class A
{
public:
    virtual void f(){cout << "A::f()" << endl;}
};

class B : public A
{
public:
    void f(){cout << "B::f()" << endl;}
};

int main()
{
    A a;
    B b;
    a.f();        // A::f()
    b.f();        // B::f()

    A *pA = &a;   
    B *pB = &b;   
    pA->f();      // A::f()
    pB->f();      // B::f()

    pA = &b;
    // pB = &a;      // not allowed
    pB = dynamic_cast<B*>(&a); // allowed but it returns NULL

    return 0;
}

Para mais informações clique aqui

por exemplo 2:

#include <iostream>

using namespace std;

class A {
public:
    virtual void print()const {cout << " A\n";}
};

class B {
public:
    virtual void print()const {cout << " B\n";}
};

class C: public A, public B {
public:
    void print()const {cout << " C\n";}
};


int main()
{

    A* a = new A;
    B* b = new B;
    C* c = new C;

    a -> print(); b -> print(); c -> print();
    b = dynamic_cast< B*>(a);  //fails
    if (b)  
       b -> print();  
    else 
       cout << "no B\n";
    a = c;
    a -> print(); //C prints
    b = dynamic_cast< B*>(a);  //succeeds
    if (b)
       b -> print();  
    else 
       cout << "no B\n";
}
Yogeesh HT
fonte