cast - conversion c++




Quando deve static_cast, dynamic_cast, const_cast e reinterpret_cast ser usado? (5)

Além das outras respostas até agora, aqui está um exemplo óbvio, em que static_cast não é suficiente para que reinterpret_cast seja necessário. Suponha que exista uma função que em um parâmetro de saída retorne ponteiros para objetos de classes diferentes (que não compartilham uma classe base comum). Um exemplo real de tal função é CoCreateInstance() (veja o último parâmetro, que é de fato void** ). Suponha que você solicite uma determinada classe de objeto a partir dessa função, para que você saiba antecipadamente o tipo do ponteiro (que você costuma fazer para objetos COM). Nesse caso, você não pode converter o ponteiro para o ponteiro em void** com static_cast : você precisa reinterpret_cast<void**>(&yourPointer) .

Em código:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    //static_cast<void**>(&pNetFwPolicy2) would give a compile error
    reinterpret_cast<void**>(&pNetFwPolicy2) );

No entanto, static_cast funciona para ponteiros simples (não ponteiros para ponteiros), portanto, o código acima pode ser reescrito para evitar reinterpret_cast (a um preço de uma variável extra) da seguinte maneira:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    &tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);

Quais são os usos apropriados de:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • (type)value conversão (type)value estilo C
  • type(value) conversão de estilo de função type(value)

Como alguém decide qual usar em quais casos específicos?


Enquanto outras respostas descrevem bem todas as diferenças entre os elencos C ++, eu gostaria de adicionar uma pequena nota por que você não deveria usar o estilo C (Type) var e Type(var) .

Para os iniciantes em C ++, os lançamentos no estilo C parecem ser a operação de superconjunto sobre os elencos C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) e alguém poderia preferí-los nos moldes C ++ . Na verdade, o elenco de estilo C é o superconjunto e mais curto para escrever.

O principal problema dos lançamentos no estilo C é que eles escondem a intenção real do criador do elenco. O estilo C pode fazer virtualmente todos os tipos de conversão de elencos normalmente seguros feitos por static_cast <> () e dynamic_cast <> () para conversões potencialmente perigosas como const_cast <> (), onde o modificador const pode ser removido para que as variáveis ​​const pode ser modificado e reinterpret_cast <> () que pode até mesmo reinterpretar valores inteiros para ponteiros.

Aqui está a amostra.

int a=rand(); // Random number.

int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.

int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.

int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.

*pa4=5; // Program crashes.

A principal razão pela qual os elencos de C ++ foram adicionados à linguagem foi permitir que um desenvolvedor esclareça suas intenções - por que ele fará esse elenco. Usando os moldes em estilo C que são perfeitamente válidos em C ++, você está tornando seu código menos legível e mais propenso a erros, especialmente para outros desenvolvedores que não criaram seu código. Então, para tornar seu código mais legível e explícito, você deve sempre preferir os lançamentos de C ++ sobre os de estilo C.

Aqui está uma breve citação do livro de Bjarne Stroustrup (o autor de C ++) The C ++ Programming Language 4a edição - página 302.

Essa conversão de estilo C é muito mais perigosa que os operadores de conversão nomeados porque a notação é mais difícil de detectar em um programa grande e o tipo de conversão pretendido pelo programador não é explícito.


Pode ajudar se você souber um pouco de internals ...

static_cast

  • O compilador C ++ já sabe como converter entre tipos de scaler como float to int. Use static_cast para eles.
  • Quando você pede ao compilador para converter do tipo A para B , o static_cast chama o construtor de B passando A como param. Alternativamente, A poderia ter um operador de conversão (isto é, A::operator B() ). Se B não tiver tal construtor, ou A não tiver um operador de conversão, você obterá um erro de tempo de compilação.
  • Cast de A* para B* sempre será bem-sucedido se A e B estiverem na hierarquia de herança (ou nula), caso contrário, você receberá um erro de compilação.
  • Pegadinha : Se você converter o ponteiro base em ponteiro derivado, mas se o objeto real não for realmente derivado, você não terá erro. Você obtém um ponteiro ruim e muito provavelmente um segfault em tempo de execução. O mesmo vale para A& para B& .
  • Gotcha : Transmita de Derived para Base ou vice-versa cria nova cópia! Para pessoas vindas de C # / Java, isso pode ser uma grande surpresa, porque o resultado é basicamente um objeto cortado criado a partir do Derived.

dynamic_cast

  • O dynamic_cast usa informações de tipo de tempo de execução para descobrir se o cast é válido. Por exemplo, (Base*) para (Derived*) pode falhar se o ponteiro não for realmente do tipo derivado.
  • Isso significa que o dynamic_cast é muito caro comparado ao static_cast!
  • Para A* a B* , se o cast for inválido, o dynamic_cast retornará nullptr.
  • Para A& para B& se o cast for inválido, o dynamic_cast lançará a exceção bad_cast.
  • Ao contrário de outros lançamentos, há sobrecarga de tempo de execução.

const_cast

  • Enquanto static_cast pode fazer não-const para const, ele não pode ir ao contrário. O const_cast pode fazer as duas coisas.
  • Um exemplo em que isso é útil é iterar por algum contêiner como set<T> que retorna apenas seus elementos como const para garantir que você não altere sua chave. No entanto, se a sua intenção é modificar os membros não-chave do objeto, então deve estar ok. Você pode usar const_cast para remover constness.
  • Outro exemplo é quando você quer implementar T& foo() assim como const T& foo() . Para evitar duplicação de código, você pode aplicar const_cast para retornar o valor de uma função de outra.

reinterpret_cast

  • Isso basicamente diz que pegue esses bytes nesse local de memória e pense nele como um objeto dado.
  • Por exemplo, você pode carregar 4 bytes de float para 4 bytes de int para ver como os bits no float se parecem.
  • Obviamente, se os dados não estiverem corretos para o tipo, você poderá obter segfault.
  • Não há sobrecarga de tempo de execução para essa conversão.

Use dynamic_cast para converter ponteiros / referências em uma hierarquia de herança.

Use static_cast para conversões de tipo comuns.

Use reinterpret_cast para reinterpret_cast de baixo nível os padrões de bits. Use com extrema cautela.

Use const_cast para expulsar const/volatile . Evite isso, a menos que você esteja preso usando uma API const-incorreta.


static_cast é o primeiro elenco que você deve tentar usar. Ele faz coisas como conversões implícitas entre tipos (como int para float ou ponteiro para void* ), e também pode chamar funções de conversão explícitas (ou implícitas). Em muitos casos, indicar explicitamente static_cast não é necessário, mas é importante observar que a sintaxe T(something) é equivalente a (T)something e deve ser evitada (mais sobre isso depois). Um T(something, something_else) é seguro, no entanto, e garantido para chamar o construtor.

static_cast também pode transmitir através de hierarquias de herança. É desnecessário ao lançar para cima (em direção a uma classe base), mas ao converter para baixo, ele pode ser usado desde que não seja transmitido através da herança virtual . Ele não faz a verificação, no entanto, e é um comportamento indefinido para static_cast em uma hierarquia para um tipo que não é realmente o tipo do objeto.

const_cast pode ser usado para remover ou adicionar const a uma variável; nenhum outro C ++ cast é capaz de removê-lo (nem mesmo reinterpret_cast ). É importante observar que modificar um valor const anteriormente só é indefinido se a variável original for const ; se você usá-lo para tirar o const de uma referência a algo que não foi declarado com const , é seguro. Isso pode ser útil ao sobrecarregar as funções de membro baseadas em const , por exemplo. Ele também pode ser usado para adicionar const a um objeto, como chamar uma sobrecarga de função de membro.

const_cast também funciona de forma semelhante no volatile , embora seja menos comum.

dynamic_cast é usado quase exclusivamente para lidar com o polimorfismo. Você pode converter um ponteiro ou uma referência a qualquer tipo polimórfico para qualquer outro tipo de classe (um tipo polimórfico tem pelo menos uma função virtual, declarada ou herdada). Você pode usá-lo para mais do que apenas lançar para baixo - você pode lançar para o lado ou até para outra corrente. O dynamic_cast procurará o objeto desejado e o retornará, se possível. Se não puder, retornará nullptr no caso de um ponteiro, ou lançará std::bad_cast no caso de uma referência.

dynamic_cast tem algumas limitações, no entanto. Não funciona se houver vários objetos do mesmo tipo na hierarquia de herança (o chamado 'diamante temido') e você não estiver usando virtual herança virtual . Também só pode passar por herança pública - sempre falhará em viajar através de herança protected ou private . Isso raramente é um problema, no entanto, como essas formas de herança são raras.

reinterpret_cast é o elenco mais perigoso e deve ser usado com muita parcimônia. Ele transforma um tipo diretamente em outro - como converter o valor de um ponteiro para outro ou armazenar um ponteiro em um int , ou todo tipo de coisas desagradáveis. Em grande parte, a única garantia que você obtém com reinterpret_cast é que, normalmente, se você converter o resultado de volta para o tipo original, obterá o mesmo valor exato (mas não se o tipo intermediário for menor que o original). Há várias conversões que o reinterpret_cast também não pode fazer. É usado principalmente para conversões e manipulações de bits particularmente estranhas, como transformar um fluxo de dados brutos em dados reais ou armazenar dados nos bits baixos de um ponteiro alinhado.

O elenco de estilo C e o elenco de estilo de função são convertidos usando (type)object ou type(object) , respectivamente. Uma conversão de estilo C é definida como o primeiro dos seguintes, que é bem-sucedido:

  • const_cast
  • static_cast (embora ignorando restrições de acesso)
  • static_cast (veja acima), então const_cast
  • reinterpret_cast
  • reinterpret_cast , em seguida, const_cast

Ele pode, portanto, ser usado como um substituto para outros lançamentos em alguns casos, mas pode ser extremamente perigoso devido à capacidade de se transformar em um reinterpret_cast , e o último deve ser preferido quando a conversão explícita é necessária, a menos que você tenha certeza de que static_cast será bem-sucedido. reinterpret_cast falhará. Mesmo assim, considere a opção mais longa e explícita.

Conjuntos de estilo C também ignoram o controle de acesso ao executar um static_cast , o que significa que eles têm a capacidade de executar uma operação que nenhum outro elenco pode. Isso é principalmente um kludge, e em minha mente é apenas mais um motivo para evitar elencos estilo C.







c++-faq