c++ - and - Elenco regular vs. static_cast vs. dynamic_cast




static_cast and dynamic_cast (6)

Esta questão já tem uma resposta aqui:

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 elencos regulares, ou seja,

MyClass *m = (MyClass *)ptr;

em todo o lugar, mas parece haver dois outros tipos de elencos, 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<MyClass *>(ptr);
MyClass *m = dynamic_cast<MyClass *>(ptr);

Elenco estático

O elenco 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 memória alocada, a gravação nesse ponteiro causará um erro de tempo de execução ou substituirá alguma memória 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 compilação.

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

Reinterpretar o elenco

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

int *r = reinterpret_cast<int*>(&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 dinâmico

Este é usado apenas para converter ponteiros de objeto e referências de objeto em outros tipos de ponteiro ou referência na hierarquia de herança. É o único elenco que garante que o objeto apontado possa ser convertido, executando uma verificação de tempo de execução que o ponteiro se refere a um objeto completo do tipo de destino. Para que essa verificação de 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 só irá gerar as informações necessárias do tipo de tempo de execução para esses 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 para base é bem-sucedida, porque o objeto Child 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 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<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 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 elenco estático teria sido preferível no primeiro exemplo, porque uma conversão derivada para a base nunca falhará.

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

No entanto, no segundo exemplo, a conversão pode ter êxito ou falhar. Ele falhará se o objeto 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 elenco dinâmico é uma escolha melhor do que o elenco estático.

// Succeeds for a MyChild object
MyChild *child = dynamic_cast<MyChild*>(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 objeto incompleto. A desreferenciação desse ponteiro pode levar a erros de tempo de execução.

// Allowed, but invalid
MyChild *child = static_cast<MyChild*>(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<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 de tempo de execução. Isso pode ocorrer, por exemplo, se a constante estiver localizada em uma seção de memória somente leitura.

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

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

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

A função 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


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 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 um objeto 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 dinâmico do objeto. Ele retorna um ponteiro nulo se o objeto referido não contiver o tipo convertido como uma classe base (quando você converter para uma referência, uma exceção bad_cast será 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ê abaixa (cast para uma classe 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 função virtual:

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

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

Elenco Regular

Esses elencos também são chamados de elenco de estilo C. Um elenco de estilo C é basicamente idêntico a experimentar uma série de seqüências de elencos em C ++, e tomar o primeiro elenco 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 apenas permitem que você faça isso, mas também permitem que você as converta com segurança em uma classe base privada, enquanto a sequência "equivalente" de static_cast lhe dará um erro em tempo de compilação.

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.


FYI, eu acredito que Bjarne Stroustrup é citado dizendo que elencos 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 elencos em estilo C. Os elencos C ++ se destacam corretamente (como deveriam; os elencos são normalmente indicativos de fazer algo ruim) e distinguem adequadamente entre os diferentes tipos de conversão que os elencos 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 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 objeto é de um determinado tipo, static_cast não pode (você simplesmente terminará com um valor inválido).

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


dynamic_cast tem verificação de tipo de tempo de execução e só funciona 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 sobre o operador static_cast .





casting