Variáveis (ou mais geralmente: “objetos” no sentido de C) não armazenam seu tipo em tempo de execução. No que diz respeito ao código da máquina, existe apenas memória não digitada. Em vez disso, as operações nesses dados interpretam os dados como um tipo específico (por exemplo, como um flutuante ou como um ponteiro). Os tipos são usados apenas pelo compilador.
Por exemplo, podemos ter uma estrutura ou classe struct Foo { int x; float y; };
e uma variável Foo f {}
. Como um campo pode acessar auto result = f.y;
ser compilado? O compilador sabe que f
é um objeto do tipo Foo
e conhece o layout de Foo
-objects. Dependendo dos detalhes específicos da plataforma, isso pode ser compilado como “Pegue o ponteiro para o início de f
, adicione 4 bytes, carregue 4 bytes e interprete esses dados como um flutuante”. Em muitos conjuntos de instruções de código de máquina (incl. x86-64) existem diferentes instruções do processador para carregar floats ou ints.
Um exemplo em que o sistema de tipos C ++ não pode controlar o tipo para nós é uma união como union Bar { int as_int; float as_float; }
. Uma união contém até um objeto de vários tipos. Se armazenarmos um objeto em uma união, este é o tipo ativo da união. Devemos apenas tentar retirar esse tipo de união, qualquer outra coisa seria um comportamento indefinido. Ou "sabemos" enquanto programamos o tipo ativo, ou podemos criar uma união marcada onde armazenamos uma tag de tipo (geralmente um enum) separadamente. Essa é uma técnica comum em C, mas como temos que manter a união e a tag de tipo em sincronia, isso é bastante propenso a erros. Um ponteiro void*
é semelhante a uma união, mas só pode conter objetos de ponteiro, exceto os ponteiros de função.
O C ++ oferece dois mecanismos melhores para lidar com objetos de tipos desconhecidos: Podemos usar técnicas orientadas a objetos para executar o apagamento de tipos (apenas interage com o objeto através de métodos virtuais para que não precisemos conhecer o objeto) tipo real), ou podemos usar std::variant
, um tipo de união de tipo seguro.
Há um caso em que o C ++ armazena o tipo de um objeto: se a classe do objeto tem algum método virtual (um “tipo polimórfico”, ou interface). O destino de uma chamada de método virtual é desconhecido em tempo de compilação e é resolvido no tempo de execução com base no tipo dinâmico do objeto ("despacho dinâmico"). A maioria dos compiladores implementa isso armazenando uma tabela de função virtual ("vtable") no início do objeto. A vtable também pode ser usada para obter o tipo do objeto em tempo de execução. Podemos, então, fazer uma distinção entre o tipo estático conhecido de uma expressão em tempo de compilação e o tipo dinâmico de um objeto em tempo de execução.
O C ++ nos permite inspecionar o tipo dinâmico de um objeto com o operador typeid()
, o que nos dá um objeto std::type_info
. O compilador sabe o tipo do objeto em tempo de compilação ou o compilador armazenou as informações de tipo necessárias dentro do objeto e pode recuperá-lo em tempo de execução.