Implementação do endereço de

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?

  • Primeiro você tem __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).
  • Isto é 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).
  • O endereço foi tirado & (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.