Pergunta

Escrevo código C e C++ há quase vinte anos, mas há um aspecto dessas linguagens que nunca entendi realmente.Obviamente, usei elencos regulares, ou seja,

MyClass *m = (MyClass *)ptr;

por todo lado, mas parece haver outros dois tipos de moldes, e 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<MyClass *>(ptr);
MyClass *m = dynamic_cast<MyClass *>(ptr);
Foi útil?

Solução

static_cast

static_cast é usado para casos em que você deseja basicamente reverter uma conversão implícita, com algumas restrições e acréscimos. static_cast não executa verificações de tempo de execução.Isto deve ser usado se você souber que se refere a um objeto 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<MyClass*>(data);
  ...
}

int main() {
  MyClass c;
  start_thread(&func, &c)  // func(&c) will be called
      .join();
}

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

elenco_dinâmico

dynamic_cast é útil quando você não sabe qual é o tipo dinâmico do objeto.Ele retorna um ponteiro nulo se o objeto referido não contém o tipo convertido como uma classe base (quando você converte para uma referência, um bad_cast exceção é lançada nesse caso).

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

Você não pode usar dynamic_cast se você fizer downcast (converter para uma classe derivada) e o tipo de argumento não for polimórfico.Por exemplo, o código a seguir não é válido porque Base não contém nenhuma função virtual:

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

Um "up-cast" (cast para a classe base) é sempre válido com ambos static_cast e dynamic_cast, e também sem conversão, pois um "up-cast" é uma conversão implícita.

Elenco normal

Esses elencos também são chamados de elenco estilo C.Uma conversão no estilo C é basicamente idêntica a experimentar uma série de sequências de conversões em C++ e pegar a primeira conversão em C++ que funcione, sem nunca considerar dynamic_cast.Escusado será dizer que isto é 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 apenas permitem que você faça isso, mas também permitem que você faça a conversão com segurança para uma classe base privada, enquanto o "equivalente" static_cast sequência daria a você um erro em tempo de compilação para isso.

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

Outras dicas

Elenco estático

A conversão estática realiza conversões entre tipos compatíveis.É semelhante ao elenco do estilo C, mas é mais restritivo.Por exemplo, a conversão no 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 memória alocada, escrever nesse ponteiro causará um erro em tempo de execução ou substituirá alguma memória adjacente.

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

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

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

Reinterpretar elenco

Para forçar a conversão do ponteiro, da mesma forma que a conversão de estilo C faz em segundo plano, a conversão de reinterpretação seria usada.

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

Essa conversão trata de 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 cautela se não puder ser evitado completamente.

Elenco dinâmico

Este é usado apenas para converter ponteiros de objetos e referências de objetos em outros ponteiros ou tipos de referência na hierarquia de herança.É a única conversão que garante que o objeto apontado possa ser convertido, realizando uma verificação em tempo de execução de que o ponteiro se refere a um objeto completo do tipo de destino.Para que esta verificação em tempo de execução seja possível, o objeto deve ser polimórfico.Ou seja, a classe deve definir ou herdar pelo menos uma função virtual.Isso ocorre porque o compilador gerará apenas as informações de tipo de tempo de execução necessárias para tais objetos.

Exemplos de elenco dinâmico

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

class MyBase 
{ 
  public:
  virtual void test() {}
};
class MyChild : public MyBase {};



int main()
{
  MyChild *child = new MyChild();
  MyBase  *base = dynamic_cast<MyBase*>(child); // ok
}

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

MyBase  *base = new MyBase();
MyChild *child = dynamic_cast<MyChild*>(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 <exception>
// …  
try
{ 
  MyChild &child = dynamic_cast<MyChild&>(*base);
}
catch(std::bad_cast &e) 
{ 
  std::cout << e.what(); // bad dynamic_cast
}

Elenco dinâmico ou estático

A vantagem de usar uma conversão dinâmica é que ela permite ao programador verificar se uma conversão foi bem-sucedida ou não durante o tempo de execução.A desvantagem é que há uma sobrecarga de desempenho associada a essa verificação.Por esta razão, usar uma conversão estática teria sido preferível no primeiro exemplo, porque uma conversão derivada para base nunca falhará.

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

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

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

Se a conversão de base para derivada tivesse sido realizada usando uma conversão estática em vez de uma conversão dinâmica, a conversão não teria falhado.Teria retornado um ponteiro que se referisse a um objeto incompleto.Desreferenciar esse ponteiro pode levar a erros em tempo de execução.

// Allowed, but invalid
MyChild *child = static_cast<MyChild*>(base);

// Incomplete MyChild object dereferenced
(*child);

Elenco constante

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

const int myConst = 5;
int *nonConst = const_cast<int*>(&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 em tempo de execução.Isto poderia ocorrer, por exemplo, se a constante estivesse localizada em uma seção da memória somente leitura.

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

Em vez disso, a conversão const é usada principalmente quando há uma função que recebe um argumento de ponteiro não constante, mesmo que não modifique o apontador.

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

A função pode então receber uma variável constante usando uma conversão const.

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

print(nonConst); // allowed

Fonte e mais explicações

Você deveria olhar o artigo Programação C++/conversão de tipo.

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

const_cast

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

static_cast

static_cast (expressão) o static_cast <> () é usado para fundir entre os tipos inteiros.'por exemplo' char-> longo, int-> curto etc.

O elenco estático também é usado para fundir ponteiros para tipos relacionados, por exemplo, lançar vazio* para o tipo apropriado.

elenco_dinâmico

O elenco dinâmico é usado para converter ponteiros e referências em tempo de execução, geralmente com o objetivo de lançar um ponteiro ou referência para cima ou para baixo em uma cadeia de herança (hierarquia de herança).

Dynamic_cast(expressão)

O tipo de destino deve ser um ponteiro ou tipo de referência, e a expressão deve avaliar um ponteiro ou referência.O elenco dinâmico funciona apenas quando o tipo de objeto ao qual a expressão se refere é compatível com o tipo de destino e a classe base possui pelo menos uma função de membro virtual.Caso contrário, e o tipo de expressão que está sendo fundido é um ponteiro, o NULL é retornado, se um lançamento dinâmico em uma referência falhar, uma exceção de bad_cast será lançada.Quando não falha, o elenco dinâmico retorna um ponteiro ou referência do tipo de destino ao objeto para qual expressão referida.

reinterpretar_cast

Reinterpretar elenco simplesmente converte um tipo bit a bit para outro.Qualquer ponteiro ou tipo integral pode ser lançado a qualquer outro com elenco reinterpretado, permitindo facilmente o uso indevido.Por exemplo, com reinterprete, ele pode, inseguamente, lançar um ponteiro inteiro para um ponteiro de cordas.

Para sua informação, acredito que Bjarne Stroustrup é citado como tendo dito que as conversões no estilo C devem ser evitadas e que você deve usar static_cast ou dynamic_cast se possível.

Perguntas frequentes sobre estilo C++ de Barne Stroustrup

Siga esse conselho para o que quiser.Estou longe de ser um guru de C++.

Evite usar moldes estilo C.

As conversões no estilo C são uma mistura de conversão const e reinterpretação e são difíceis de localizar e substituir em seu código.Um programador de aplicativos C++ deve evitar conversão no estilo C.

As conversões no estilo C combinam const_cast, static_cast e reinterpret_cast.

Eu gostaria que o C++ não tivesse conversões no estilo C.As conversões de C++ se destacam adequadamente (como deveriam;casts são normalmente indicativos de fazer algo ruim) e distinguir adequadamente entre os diferentes tipos de conversão que os casts realizam.Eles também permitem que funções de aparência semelhante sejam escritas, por ex.boost::lexical_cast, o que é muito bom do ponto de vista da consistência.

dynamic_cast possui verificação de tipo em tempo de execução e funciona apenas com referências e ponteiros, 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 Operador static_cast.

dynamic_cast suporta apenas tipos de ponteiro e referência.Ele retorna 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.Por isso, dynamic_cast pode ser usado para verificar se um objeto é de um determinado tipo, static_cast não pode (você simplesmente acabará com um valor inválido).

As conversões de estilo C (e outras) foram abordadas nas outras respostas.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top