Por que usar static_cast <int> (x) em vez de (int) x?

667

Ouvi dizer que a static_castfunção deve ser preferida ao estilo C ou simples ao estilo de função. Isso é verdade? Por quê?

Tommy Herbert
fonte
36
Objeção sua honra, pediu e respondeu .
Graeme Perrow
25
Eu discordo, essa outra pergunta foi sobre a descrição das diferenças entre os lançamentos introduzidos em C ++. Esta pergunta é sobre a real utilidade do static_cast, que é um pouco diferente.
Vincent Robert
2
Certamente poderíamos mesclar as duas perguntas, mas o que precisamos preservar desse encadeamento é a vantagem de usar funções sobre a conversão no estilo C, que atualmente é mencionado apenas em uma resposta de uma linha no outro encadeamento, sem votos .
Tommy Herbert
6
Esta pergunta é sobre tipos "internos", como int, enquanto que essa pergunta é sobre tipos de classe. Parece uma diferença significativa o suficiente para merecer uma explicação separada.
9
static_cast é realmente um operador, não uma função.
25413 ThomasMcLeod

Respostas:

633

A principal razão é que moldes clássicos C não fazem distinção entre o que chamamos static_cast<>(), reinterpret_cast<>(), const_cast<>(), e dynamic_cast<>(). Essas quatro coisas são completamente diferentes.

A static_cast<>()é geralmente seguro. Há uma conversão válida no idioma ou um construtor apropriado que o torna possível. A única vez que é um pouco arriscado é quando você baixa para uma classe herdada; você deve se certificar de que o objeto é realmente o descendente que você afirma ser, por meios externos ao idioma (como uma bandeira no objeto). A dynamic_cast<>()é seguro desde que o resultado seja verificado (ponteiro) ou uma possível exceção seja levada em consideração (referência).

A reinterpret_cast<>()(ou a const_cast<>()), por outro lado, é sempre perigoso. Você diz ao compilador: "confie em mim: eu sei que isso não se parece com um foo(parece que não é mutável), mas é".

O primeiro problema é que é quase impossível dizer qual deles ocorrerá em um elenco no estilo C, sem examinar pedaços de código grandes e dispersos e conhecer todas as regras.

Vamos assumir o seguinte:

class CDerivedClass : public CMyBase {...};
class CMyOtherStuff {...} ;

CMyBase  *pSomething; // filled somewhere

Agora, esses dois são compilados da mesma maneira:

CDerivedClass *pMyObject;
pMyObject = static_cast<CDerivedClass*>(pSomething); // Safe; as long as we checked

pMyObject = (CDerivedClass*)(pSomething); // Same as static_cast<>
                                     // Safe; as long as we checked
                                     // but harder to read

No entanto, vamos ver esse código quase idêntico:

CMyOtherStuff *pOther;
pOther = static_cast<CMyOtherStuff*>(pSomething); // Compiler error: Can't convert

pOther = (CMyOtherStuff*)(pSomething);            // No compiler error.
                                                  // Same as reinterpret_cast<>
                                                  // and it's wrong!!!

Como você pode ver, não há uma maneira fácil de distinguir entre as duas situações sem saber muito sobre todas as classes envolvidas.

O segundo problema é que as projeções no estilo C são muito difíceis de localizar. Em expressões complexas, pode ser muito difícil visualizar projeções no estilo C. É praticamente impossível escrever uma ferramenta automatizada que precise localizar conversões no estilo C (por exemplo, uma ferramenta de pesquisa) sem um front-end completo do compilador C ++. Por outro lado, é fácil procurar por "static_cast <" ou "reinterpret_cast <".

pOther = reinterpret_cast<CMyOtherStuff*>(pSomething);
      // No compiler error.
      // but the presence of a reinterpret_cast<> is 
      // like a Siren with Red Flashing Lights in your code.
      // The mere typing of it should cause you to feel VERY uncomfortable.

Isso significa que, não apenas os lançamentos no estilo C são mais perigosos, mas é muito mais difícil encontrá-los para garantir que eles estejam corretos.

Euro Micelli
fonte
30
Você não deve usar static_castpara derrubar uma hierarquia de herança, mas sim dynamic_cast. Isso retornará o ponteiro nulo ou um ponteiro válido.
precisa saber é o seguinte
41
@ David Thornley: Eu concordo, geralmente. Acho que indiquei as advertências para usar static_castnessa situação. dynamic_castpode ser mais seguro, mas nem sempre é a melhor opção. Às vezes, você sabe que um ponteiro aponta para um determinado subtipo, por meio opaco ao compilador, e a static_casté mais rápido. Em pelo menos alguns ambientes, dynamic_castrequer suporte opcional do compilador e custo de tempo de execução (habilitando o RTTI), e talvez você não queira habilitá-lo apenas para algumas verificações, você mesmo pode fazer. O RTTI do C ++ é apenas uma solução possível para o problema.
Euro Micelli
18
Sua reivindicação sobre o elenco C é falsa. Todos os lançamentos em C são conversões de valor, aproximadamente comparáveis ​​ao C ++ static_cast. O equivalente C de reinterpret_casté *(destination_type *)&, ou seja, pegar o endereço do objeto, converter esse endereço em um ponteiro para um tipo diferente e, em seguida, desreferenciar. Excepto no caso de tipos de caracteres ou de certos tipos de struct para o qual C define o comportamento dessa construção, que geralmente resulta em comportamento indefinido em C.
R .. GitHub parar de ajudar ICE
11
Sua resposta precisa aborda o corpo da postagem. Eu estava procurando uma resposta para o título "por que usar static_cast <int> (x) em vez de (int) x". Ou seja, para o tipo int(e intsozinho), por que usar static_cast<int>vs. (int)como o único benefício parece ser com variáveis ​​de classe e ponteiros. Solicite que você elabore sobre isso.
chux - Restabelece Monica
31
@chux, pois int dynamic_castnão se aplica, mas todas as outras razões permanecem. Por exemplo: digamos que vseja um parâmetro de função declarado como float, então (int)vé static_cast<int>(v). Mas se você alterar o parâmetro para float*, (int)vem silêncio torna-se reinterpret_cast<int>(v)enquanto static_cast<int>(v)é ilegal e corretamente pego pelo compilador.
Euro Micelli
115

Uma dica pragmática: você pode pesquisar facilmente a palavra-chave static_cast no seu código-fonte se planeja arrumar o projeto.

Karl
fonte
3
também é possível pesquisar usando os colchetes, como "(int)", mas uma boa resposta e um motivo válido para usar a conversão no estilo C ++.
Mike
4
@ Mike que encontrará falsos positivos - uma declaração de função com um único intparâmetro.
Nathan Osman
1
Isso pode dar falsos negativos: se você estiver pesquisando em uma base de código em que não é o único autor, não encontrará elencos no estilo C que outros possam ter introduzido por alguns motivos.
Ruslan
7
Como isso ajudaria a arrumar o projeto?
Bilow
Você não procuraria por static_cast, porque provavelmente é o correto. Você deseja filtrar static_cast, enquanto pesquisa reinterpret_cast, const_cast e talvez mesmo dynamic_cast, pois isso indica locais que podem ser redesenhados. O elenco C mistura todos juntos e não fornece o motivo do elenco.
Dragan
78

Em resumo :

  1. static_cast<>() dá a você a capacidade de verificar o tempo de compilação, o elenco do estilo C não.
  2. static_cast<>()pode ser visto facilmente em qualquer lugar dentro de um código fonte C ++; em contraste, o elenco de C_Style é mais difícil de detectar.
  3. As intenções são transmitidas muito melhor usando conversões de C ++.

Mais explicações :

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 aponte 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 em tempo de execução ou substituirá parte da memória adjacente.

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

Ao contrário da conversão em estilo C, a conversão estática permitirá que o compilador verifique se os tipos de dados de ponteiro e ponta são compatíveis, o que permite ao programador capturar essa atribuição incorreta de ponteiro durante a compilação.

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

Leia mais em:
Qual é a diferença entre static_cast <> e conversão de estilo C
e conversão
regular vs. static_cast vs. dynamic_cast

Rika
fonte
21
Eu discordo que static_cast<>()é mais legível. Quero dizer, às vezes é, mas na maioria das vezes - especialmente em tipos inteiros básicos - é horrível e desnecessariamente detalhado. Por exemplo: Esta é uma função que troca os bytes de uma palavra de 32 bits. Seria quase impossível ler usando static_cast<uint##>()moldes, mas é bastante fácil de entender usando (uint##)moldes. Imagem do código: imgur.com/NoHbGve
Todd Lehman
3
@ToddLehman: Obrigado, mas eu também não disse always. (mas na maioria das vezes sim) Há casos em que o estilo c é muito mais legível. Essa é uma das razões pelas quais a conversão de estilo c ainda está ativa e ativa no im ++ do c ++. :) A propósito, esse foi um exemplo muito bom
Rika 23/05
8
O código @ToddLehman nessa imagem usa duas transmissões encadeadas ( (uint32_t)(uint8_t)) para atingir os bytes além dos mais baixos. Para isso há bit a bit e ( 0xFF &). O uso de elencos está ofuscando a intenção.
Öö Tiib
28

A questão é maior do que apenas usar a conversão wither static_cast ou estilo C, porque há coisas diferentes que acontecem ao usar moldes no estilo C. Os operadores de conversão de C ++ destinam-se a tornar essas operações mais explícitas.

Na superfície, static_cast e estilo C lançam a mesma coisa, por exemplo, ao converter um valor para outro:

int i;
double d = (double)i;                  //C-style cast
double d2 = static_cast<double>( i );  //C++ cast

Ambos convertem o valor inteiro em um dobro. No entanto, ao trabalhar com ponteiros, as coisas ficam mais complicadas. alguns exemplos:

class A {};
class B : public A {};

A* a = new B;
B* b = (B*)a;                                  //(1) what is this supposed to do?

char* c = (char*)new int( 5 );                 //(2) that weird?
char* c1 = static_cast<char*>( new int( 5 ) ); //(3) compile time error

Neste exemplo (1) talvez esteja OK porque o objeto apontado por A é realmente uma instância de B. Mas e se você não souber nesse ponto do código o que realmente aponta? (2) talvez perfeitamente legal (você só quer olhar para um byte do número inteiro), mas também pode ser um erro; nesse caso, um erro seria bom, como (3). Os operadores de conversão de C ++ destinam-se a expor esses problemas no código, fornecendo erros em tempo de compilação ou tempo de execução, quando possível.

Portanto, para estrita "conversão de valor", você pode usar static_cast. Se você desejar a conversão polimórfica de ponteiros em tempo de execução, use dynamic_cast. Se você realmente deseja esquecer os tipos, pode usar reintrepret_cast. E para jogar const pela janela, const_cast.

Eles apenas tornam o código mais explícito, para que você saiba o que estava fazendo.

Dusty Campbell
fonte
26

static_castsignifica que você não pode acidentalmente const_castou reinterpret_cast, o que é uma coisa boa.

DrPizza
fonte
4
Vantagens adicionais (embora um pouco menores) sobre o elenco do estilo C é que ele se destaca mais (fazer algo potencialmente ruim deve parecer feio) e é mais aceitável.
Michael Burr
4
grep-skill é sempre uma vantagem, no meu livro.
Branan 22/09/08
7
  1. Permite que as transmissões sejam encontradas facilmente em seu código usando grep ou ferramentas semelhantes.
  2. Torna explícito o tipo de elenco que você está fazendo e o envolvimento da ajuda do compilador na imposição. Se você deseja eliminar apenas a constância, use o const_cast, o que não permitirá que você faça outros tipos de conversão.
  3. As conversões são inerentemente feias - você como programador está anulando como o compilador trataria seu código normalmente. Você está dizendo ao compilador: "Eu sei melhor que você". Sendo esse o caso, faz sentido que a execução de um elenco deva ser uma coisa moderadamente dolorosa e que eles se destacem no seu código, pois são uma provável fonte de problemas.

Consulte Introdução eficaz ao C ++

JohnMcG
fonte
Concordo plenamente com isso nas aulas, mas o uso do estilo C ++ para tipos de POD faz algum sentido?
Zachary Kraus
Acho que sim. Todos os três motivos se aplicam aos PODs, e é útil ter apenas uma regra, em vez de separar as de classes e PODs.
JohnMcG 12/09
Interessante, talvez eu precise modificar como faço minhas transmissões no código futuro para tipos de POD.
Zachary Kraus
7

É sobre quanta segurança de tipo você deseja impor.

Quando você escreve (bar) foo(o que equivale a reinterpret_cast<bar> foose você não forneceu um operador de conversão de tipos), está dizendo ao compilador para ignorar a segurança de tipos e faça o que é solicitado.

Ao escrever, static_cast<bar> foovocê solicita ao compilador que verifique pelo menos se a conversão de tipo faz sentido e, para tipos integrais, insira algum código de conversão.


EDIT 2014-02-26

Escrevi esta resposta há mais de 5 anos e entendi errado. (Ver comentários.) Mas ainda recebe votos!

Pitarou
fonte
8
(bar) foo não é equivalente a reinterpret_cast <bar> (foo). As regras para "(TYPE) expr" são que ele escolherá a conversão de estilo C ++ apropriada a ser usada, o que pode incluir reinterpret_cast.
Richard Corden
Bom ponto. Euro Micelli deu a resposta definitiva para esta pergunta.
Pitarou 20/09/08
1
Além disso, é static_cast<bar>(foo), entre parênteses. O mesmo para reinterpret_cast<bar>(foo).
LF
6

As conversões de estilo C são fáceis de perder em um bloco de código. As conversões no estilo C ++ não são apenas melhores práticas; eles oferecem um grau muito maior de flexibilidade.

reinterpret_cast permite integrais para conversões de tipo de ponteiro, no entanto, pode ser inseguro se mal utilizado.

static_cast oferece uma boa conversão para tipos numéricos, por exemplo, de enums para ints ou ints para floats ou quaisquer tipos de dados dos quais você confia. Ele não executa nenhuma verificação de tempo de execução.

O dynamic_cast, por outro lado, executará essas verificações sinalizando quaisquer atribuições ou conversões ambíguas. Funciona apenas em ponteiros e referências e gera uma sobrecarga.

Existem alguns outros, mas estes são os principais que você encontrará.

Konrad
fonte
4

static_cast, além de manipular ponteiros para classes, também pode ser usado para realizar conversões explicitamente definidas em classes, bem como para executar conversões padrão entre tipos fundamentais:

double d = 3.14159265;
int    i = static_cast<int>(d);
prakash
fonte
4
Por que alguém escreveria static_cast<int>(d), porém, quando (int)dé tão mais conciso e legível? (Quero dizer, no caso dos tipos básicos, e não ponteiros de objeto.)
Todd Lehman
@ gd1 - Por que alguém colocaria consistência acima da legibilidade? (na verdade, meio a sério)
Todd Lehman
2
@ToddLehman: Eu, considerando que abrir uma exceção para certos tipos, apenas porque eles são especiais para você, não faz sentido para mim, e eu também discordo de sua noção de legibilidade. Mais curto não significa mais legível, como vejo na imagem que você postou em outro comentário.
Gd1
2
static_cast é uma decisão clara e consciente de fazer um tipo muito particular de conversão. Por isso, aumenta a clareza de intenção. Também é muito útil como marcador para pesquisar nos arquivos de origem conversões em um exercício de revisão de código, bug ou atualização.
Persixty
1
@ToddLehman contraponto: Por que alguém escreveria (int)dquando int{d}é muito mais legível? A ()sintaxe do construtor ou da função, se você tiver , não é tão rápida para se transformar em um labirinto de pesadelos de parênteses em expressões complexas. Nesse caso, seria em int i{d}vez de int i = (int)d. Muito melhor IMO. Dito isto, quando eu só preciso de uma expressão temporária, eu uso static_caste nunca usei moldes de construtores, não acho. Eu só uso (C)castsquando apressadamente escrito depuração couts ...
underscore_d