Desconhecendo previamente a existência do std::addressof
, por que existe, faz sentido para mim: como uma maneira de pegar o endereço na presença de um operator&
sobrecarregado operator&
. A implementação, no entanto, é um pouco mais opaca. Do gcc 4.7.1
:
template inline _Tp* __addressof(_Tp& __r) _GLIBCXX_NOEXCEPT { return reinterpret_cast (&const_cast(reinterpret_cast(__r))); }
O reinterpret_cast
é óbvio. O resto é magia negra. Alguém pode quebrar como isso realmente funciona?
__r
que é do tipo _Tp&
reinterpret_cast
para um char&
para garantir a possibilidade de tomar seu endereço posteriormente sem temer um operator&
sobrecarregado operator&
no tipo original; na verdade, ele é convertido para const volatile char&
porque reinterpret_cast
sempre pode include legalmente const
e qualificadores volatile
mesmo que eles não estejam presentes, mas não pode removê-los se estiverem presentes (isso garante que, independentemente dos qualificadores que _Tp
tinha originalmente, eles não interferir com o casting). const_cast
para apenas char&
, removendo os qualificadores (legalmente agora! const_cast
pode fazer o que reinterpret_cast
não pôde com relação aos qualificadores). &
(agora temos um char*
simples char*
) reinterpret_cast
‘de volta para _Tp*
(que inclui os qualificadores const
e volatile
originais, se houver). Edit: desde que minha resposta foi aceita, eu vou ser completo e adicionar que a escolha de char
como um tipo intermediário é devido a problemas de alinhamento, a fim de evitar o desencadeamento de comportamento indefinido. Veja os comentários do @ JamesKanze (sob a pergunta) para uma explicação completa. Obrigado James por explicar isso tão claramente.
Na verdade, é bem simples quando você pensa sobre isso, para obter o endereço real de um object / function na precisão de um operator&
sobrecarregado operator&
você precisará tratar o object como algo diferente do que realmente é, algum tipo que não pode ter um operador sobrecarregado. .. um tipo intrínseco (como char
).
Um char
não tem alinhamento e pode residir em qualquer lugar que qualquer outro object possa, com isso dito; lançar um object para uma referência a char é um bom começo.
Mas e a magia negra envolvida ao fazer reinterpret_cast
?
Para reinterpretar o ponteiro retornado da implementação do addressof
, eventualmente iremos descartar os qualificadores como const
e volatile
(para obter um char
referência simples). Esses dois podem ser adicionados facilmente com reinterpret_cast
, mas pedir para removê-los é ilegal.
T1 const a; reinterpret_cast (a); /* error: reinterpret_cast from type '...' to type '...' casts away qualifiers */
É um truque “melhor prevenir do que remediar” .. “Vamos adicioná-los, apenas no caso, vamos removê-los mais tarde.”
Mais tarde, const_cast
os qualificadores ( const e volátil ) com const_cast
para terminar com uma referência simples a char
, esse resultado é, como etapa final, transformado em um ponteiro para qualquer tipo que passamos para nossa implementação.
Uma pergunta relevante nesse estágio é por que nós não const_cast
o uso de reinterpret_cast
e fomos diretamente para o const_cast
? isso também tem uma resposta simples: const_cast
pode adicionar / remover qualificadores, mas não pode alterar o tipo subjacente.
T1 a; const_cast (a); /* error: invalid const_cast from type 'T1*' to type 'T2*' */
pode não ser fácil como torta, mas com certeza é gostosa quando você a pega.
A versão curta:
operator&
não pode ser sobrecarregado por char
. Portanto, o tipo está sendo convertido em uma referência de char
para obter o que é garantido como o endereço verdadeiro.
Essa conversão é feita em dois lançamentos devido às restrições em const_cast
e reinterpret_cast
.
A versão mais longa:
Está realizando três castings sequenciais.
reinterpret_cast
Isso é efetivamente converter para um char&
. O const
e volatile
só existem porque _Tp
pode ser const
ou volatile
, e reinterpret_cast
pode adicionar esses, mas seria incapaz de removê- los.
const_cast
Agora o const
e volatile
foram removidos. const_cast
pode fazer isso.
reinterpret_cast<_Tp*> &(result)
Agora o endereço é obtido e o tipo é convertido novamente em um ponteiro para o tipo original.
De dentro para fora:
Primeiro ele lança o tipo __r
para um const volatile char&
: ele está lançando para um char&
só porque é um tipo que com certeza não tem um operator&
sobrecarregado operator&
que faz algo estranho. O const volatile
está lá porque essas são restrições, elas podem ser adicionadas mas não removidas com reinterpret_cast
. _Tp
pode já ter sido const
e / ou volatile
, caso em que um ou ambos foram necessários neste casting. Se isso não aconteceu, o casting só os adicionou desnecessariamente, mas está escrito para o casting mais restritivo.
Em seguida, para tirar o const volatile
você precisa de um const_cast
, que leva à próxima parte … const_cast
.
De lá, eles simplesmente pegam o endereço e o lançam no tipo que você quer, um _Tp*
. Observe que _Tp
pode ser const
e / ou volatile
, o que significa que essas coisas podem ser adicionadas novamente neste momento.