Elenco regular vs. static_cast vs. dynamic_cast

Eu tenho escrito código C e C ++ por quase vinte anos, mas há um aspecto dessas linguagens que eu nunca entendi de verdade. Eu obviamente usei castings regulares, ou seja,

MyClass *m = (MyClass *)ptr; 

em todo o lugar, mas parece haver dois outros tipos de castings, e eu não sei a diferença. Qual é a diferença entre as seguintes linhas de código?

 MyClass *m = (MyClass *)ptr; MyClass *m = static_cast(ptr); MyClass *m = dynamic_cast(ptr); 

static_cast

static_cast é usado para casos em que você basicamente deseja reverter uma conversão implícita, com algumas restrições e adições. static_cast não executa verificações de tempo de execução. Isso deve ser usado se você souber que se refere a um object de um tipo específico e, portanto, uma verificação seria desnecessária. Exemplo:

 void func(void *data) { // Conversion from MyClass* -> void* is implicit MyClass *c = static_cast(data); ... } int main() { MyClass c; start_thread(&func, &c) // func(&c) will be called .join(); } 

Neste exemplo, você sabe que passou um object MyClass e, portanto, não há necessidade de uma verificação de tempo de execução para garantir isso.

dynamic_cast

dynamic_cast é útil quando você não sabe qual é o tipo dynamic do object. Ele retorna um ponteiro nulo se o object referido não contiver o tipo convertido como uma class base (quando você converter para uma referência, uma exceção bad_cast será lançada nesse caso).

 if (JumpStm *j = dynamic_cast(&stm)) { ... } else if (ExprStm *e = dynamic_cast(&stm)) { ... } 

Você não pode usar dynamic_cast se você abaixa (cast para uma class derivada) e o tipo de argumento não é polimórfico. Por exemplo, o código a seguir não é válido, porque Base não contém nenhuma function virtual:

 struct Base { }; struct Derived : Base { }; int main() { Derived d; Base *b = &d; dynamic_cast(b); // Invalid } 

Um “up-cast” (conversão para a class base) é sempre válido com static_cast e dynamic_cast , e também sem casting, já que um “up-cast” é uma conversão implícita.

Elenco Regular

Esses castings também são chamados de casting de estilo C. Um casting de estilo C é basicamente idêntico a experimentar uma série de seqüências de castings em C ++, e tomar o primeiro casting de C ++ que funciona, sem nunca considerar dynamic_cast . Escusado será dizer, isso é muito mais poderoso, pois combina todos os const_cast , static_cast e reinterpret_cast , mas também é inseguro, porque não usa dynamic_cast .

Além disso, as conversões no estilo C não só permitem que você faça isso, mas também permitem que você as converta com segurança em uma class base privada, enquanto a seqüência “equivalente” de static_cast lhe dará um erro em tempo de compilation.

Algumas pessoas preferem o estilo C por causa de sua brevidade. Eu os uso apenas para conversões numéricas e uso de conversões C ++ apropriadas quando os tipos definidos pelo usuário estão envolvidos, pois eles fornecem uma verificação mais rigorosa.

Elenco estático

O casting estático realiza conversões entre tipos compatíveis. É semelhante ao modelo de estilo C, mas é mais restritivo. Por exemplo, o conversão de estilo C permitiria que um ponteiro inteiro apontasse para um caractere.

 char c = 10; // 1 byte int *p = (int*)&c; // 4 bytes 

Como isso resulta em um ponteiro de 4 bytes apontando para 1 byte de memory alocada, a gravação nesse ponteiro causará um erro de tempo de execução ou replaceá alguma memory adjacente.

 *p = 5; // run-time error: stack corruption 

Em contraste com a conversão de estilo C, a conversão estática permitirá que o compilador verifique se os tipos de dados de ponteiro e ponteiro são compatíveis, o que permite que o programador capture essa atribuição incorreta de ponteiro durante a compilation.

 int *q = static_cast(&c); // compile-time error 

Reinterpretar o casting

Para forçar a conversão do ponteiro, da mesma maneira que o modelo do estilo C faz no plano de fundo, o casting de reinterpretação seria usado no lugar.

 int *r = reinterpret_cast(&c); // forced conversion 

Essa conversão manipula conversões entre determinados tipos não relacionados, como de um tipo de ponteiro para outro tipo de ponteiro incompatível. Ele simplesmente executará uma cópia binária dos dados sem alterar o padrão de bits subjacente. Observe que o resultado dessa operação de baixo nível é específico do sistema e, portanto, não é portátil. Deve ser usado com cuidado se não puder ser totalmente evitado.

Elenco dynamic

Este é usado apenas para converter pointers de object e referências de object em outros tipos de ponteiro ou referência na hierarquia de inheritance. É o único casting que garante que o object apontado possa ser convertido, executando uma verificação de tempo de execução que o ponteiro se refere a um object completo do tipo de destino. Para que essa verificação de tempo de execução seja possível, o object deve ser polimórfico. Ou seja, a class deve definir ou herdar pelo menos uma function virtual. Isso ocorre porque o compilador só irá gerar as informações necessárias do tipo de tempo de execução para esses objects.

Exemplos de casting dynamic

No exemplo abaixo, um ponteiro MyChild é convertido em um ponteiro MyBase usando uma conversão dinâmica. Essa conversão derivada para base é bem-sucedida, porque o object Child inclui um object Base completo.

 class MyBase { public: virtual void test() {} }; class MyChild : public MyBase {}; int main() { MyChild *child = new MyChild(); MyBase *base = dynamic_cast(child); // ok } 

O próximo exemplo tenta converter um ponteiro MyBase em um ponteiro MyChild. Como o object Base não contém um object Child completo, essa conversão de ponteiro falhará. Para indicar isso, a conversão dinâmica retorna um ponteiro nulo. Isso fornece uma maneira conveniente de verificar se uma conversão foi bem-sucedida durante o tempo de execução.

 MyBase *base = new MyBase(); MyChild *child = dynamic_cast(base); if (child == 0) std::cout < < "Null pointer returned"; 

Se uma referência for convertida em vez de um ponteiro, a conversão dinâmica falhará lançando uma exceção bad_cast. Isso precisa ser tratado usando uma instrução try-catch.

 #include  // … try { MyChild &child = dynamic_cast(*base); } catch(std::bad_cast &e) { std::cout < < e.what(); // bad dynamic_cast } 

Elenco dynamic ou estático

A vantagem de usar uma conversão dinâmica é que ela permite que o programador verifique se uma conversão foi bem-sucedida durante o tempo de execução. A desvantagem é que há uma sobrecarga de desempenho associada a essa verificação. Por esse motivo, o uso de um casting estático teria sido preferível no primeiro exemplo, porque uma conversão derivada para a base nunca falhará.

 MyBase *base = static_cast(child); // ok 

No entanto, no segundo exemplo, a conversão pode ter êxito ou falhar. Ele falhará se o object MyBase contiver uma instância MyBase e será bem-sucedido se contiver uma instância MyChild. Em algumas situações, isso pode não ser conhecido até o tempo de execução. Quando este é o caso, o casting dynamic é uma escolha melhor do que o casting estático.

 // Succeeds for a MyChild object MyChild *child = dynamic_cast(base); 

Se a conversão de base para derivada tivesse sido executada usando uma conversão estática em vez de uma conversão dinâmica, a conversão não teria falhado. Ele teria retornado um ponteiro que se referia a um object incompleto. A desreferenciação desse ponteiro pode levar a erros de tempo de execução.

 // Allowed, but invalid MyChild *child = static_cast(base); // Incomplete MyChild object dereferenced (*child); 

Elenco Const

Este é usado principalmente para adicionar ou remover o modificador const de uma variável.

 const int myConst = 5; int *nonConst = const_cast(&myConst); // removes const 

Embora const cast permita que o valor de uma constante seja alterado, isso ainda é um código inválido que pode causar um erro de tempo de execução. Isso pode ocorrer, por exemplo, se a constante estiver localizada em uma seção de memory somente leitura.

 *nonConst = 10; // potential run-time error 

Const cast é usado principalmente quando há uma function que recebe um argumento de ponteiro não constante, mesmo que não modifique o pointee.

 void print(int *p) { std::cout < < *p; } 

A function pode então ser passada por uma variável constante usando uma constante.

 print(&myConst); // error: cannot convert // const int* to int* print(nonConst); // allowed 

Fonte e mais explicações

Você deve olhar para o artigo C ++ Programming / Type Casting .

Ele contém uma boa descrição de todos os diferentes tipos de casting. O seguinte tirado do link acima:

const_cast

const_cast (expressão) O const_cast <> () é usado para adicionar / remover const (ness) (ou volatilidade) de uma variável.

static_cast

static_cast (expression) O static_cast <> () é usado para transmitir entre os tipos inteiros. ‘eg’ char-> long, int-> short etc.

A conversão estática também é usada para converter pointers para tipos relacionados, por exemplo, convertendo void * no tipo apropriado.

dynamic_cast

A conversão dinâmica é usada para converter pointers e referências em tempo de execução, geralmente com o propósito de converter um ponteiro ou referência para cima ou para baixo em uma cadeia de inheritance (hierarquia de inheritance).

dynamic_cast (expressão)

O tipo de destino deve ser um ponteiro ou um tipo de referência e a expressão deve ser avaliada como um ponteiro ou referência. A conversão dinâmica funciona apenas quando o tipo de object ao qual a expressão se refere é compatível com o tipo de destino e a class base possui pelo menos uma function de membro virtual. Se não, e o tipo de expressão sendo convertida é um ponteiro, NULL é retornado, se uma conversão dinâmica falhar em uma referência, uma exceção bad_cast é lançada. Quando não falha, a conversão dinâmica retorna um ponteiro ou referência do tipo de destino para o object ao qual a expressão se refere.

reinterpret_cast

Reinterpretar conversão simplesmente lança um tipo bit a bit para outro. Qualquer ponteiro ou tipo integral pode ser lançado para qualquer outro com reinterpretação, permitindo facilmente o mau uso. Por exemplo, com a reinterpretação, um deles poderia, de maneira insegura, converter um ponteiro inteiro em um ponteiro de string.

Evite usar castings em estilo C.

Os moldes em estilo C são uma mistura de const e reinterpret cast, e é difícil encontrar e replace em seu código. Um programador de aplicativo C ++ deve evitar casting de estilo C.

FYI, eu acredito que Bjarne Stroustrup é citado dizendo que castings no estilo C devem ser evitados e que você deve usar static_cast ou dynamic_cast se for possível.

Perguntas frequentes sobre o estilo C ++ de Barne Stroustrup

Aceite esse conselho para o que você quiser. Estou longe de ser um guru em C ++.

Os estilos de estilo C combinam const_cast, static_cast e reinterpret_cast.

Eu gostaria que o C ++ não tivesse castings em estilo C. Os castings C ++ se destacam corretamente (como deveriam; os castings são normalmente indicativos de fazer algo ruim) e distinguem adequadamente entre os diferentes tipos de conversão que os castings executam. Eles também permitem que funções similares sejam escritas, por exemplo, boost :: lexical_cast, o que é muito bom do ponto de vista da consistência.

dynamic_cast tem verificação de tipo de tempo de execução e só funciona com referências e pointers, enquanto static_cast não oferece verificação de tipo de tempo de execução. Para obter informações completas, consulte o artigo do MSDN sobre o operador static_cast .

dynamic_cast só suporta tipos de ponteiro e referência. Ele retornará NULL se a conversão for impossível se o tipo for um ponteiro ou lançar uma exceção se o tipo for um tipo de referência. Portanto, dynamic_cast pode ser usado para verificar se um object é de um determinado tipo, static_cast não pode (você simplesmente terminará com um valor inválido).

C-estilo (e outros) castings foram abordados nas outras respostas.