Considérez le programme suivant:
struct ghost
{
// ghosts like to pretend that they don't exist
ghost* operator&() const volatile { return 0; }
};
int main()
{
ghost clyde;
ghost* clydes_address = &clyde; // darn; that's not clyde's address :'(
}
Comment obtenir l'adresse de clyde
?
Je recherche une solution qui fonctionnera aussi bien pour tous les types d'objets. Une solution C++ 03 serait bien, mais je suis également intéressé par les solutions C++ 11. Si possible, évitons tout comportement spécifique à l'implémentation.
Je connais le modèle de fonction std::addressof
De C++ 11, mais je ne suis pas intéressé à l'utiliser ici: j'aimerais comprendre comment un implémenteur de bibliothèque standard pourrait implémenter ce modèle de fonction.
Mise à jour: en C++ 11, on peut utiliser std::addressof
Au lieu de boost::addressof
.
Copions d'abord le code de Boost, moins le travail du compilateur autour des bits:
template<class T>
struct addr_impl_ref
{
T & v_;
inline addr_impl_ref( T & v ): v_( v ) {}
inline operator T& () const { return v_; }
private:
addr_impl_ref & operator=(const addr_impl_ref &);
};
template<class T>
struct addressof_impl
{
static inline T * f( T & v, long ) {
return reinterpret_cast<T*>(
&const_cast<char&>(reinterpret_cast<const volatile char &>(v)));
}
static inline T * f( T * v, int ) { return v; }
};
template<class T>
T * addressof( T & v ) {
return addressof_impl<T>::f( addr_impl_ref<T>( v ), 0 );
}
Que se passe-t-il si nous passons une référence à la fonction ?
Remarque: addressof
ne peut pas être utilisé avec un pointeur pour fonctionner
En C++ si void func();
est déclaré, alors func
est une référence à une fonction ne prenant aucun argument et ne retournant aucun résultat. Cette référence à une fonction peut être trivialement convertie en un pointeur vers la fonction - à partir de @Konstantin
: Selon 13.3.3.2, T &
Et T *
Sont indiscernables pour les fonctions. La première est une conversion d'identité et la seconde est une conversion de fonction en pointeur, toutes deux ayant le rang "Correspondance exacte" (13.3.3.1.1 tableau 9).
La référence à la fonction passe par addr_impl_ref
, Il y a une ambiguïté dans la résolution de surcharge pour le choix de f
, qui est résolue grâce à l'argument factice 0
, Qui est d'abord un int
et pourrait être promu en long
(conversion intégrale).
Ainsi, nous renvoyons simplement le pointeur.
Que se passe-t-il si nous transmettons un type à un opérateur de conversion?
Si l'opérateur de conversion donne un T*
Alors nous avons une ambiguïté: pour f(T&,long)
une Promotion Intégrale est requise pour le deuxième argument tandis que pour f(T*,int)
l'opérateur de conversion est appelé le premier (merci à @litb)
C'est alors que addr_impl_ref
Entre en jeu. La norme C++ exige qu'une séquence de conversion puisse contenir au plus une conversion définie par l'utilisateur. En encapsulant le type dans addr_impl_ref
Et en forçant déjà l'utilisation d'une séquence de conversion, nous "désactivons" tout opérateur de conversion fourni avec le type.
Ainsi, la surcharge f(T&,long)
est sélectionnée (et la promotion intégrale effectuée).
Que se passe-t-il pour tout autre type?
Ainsi, la surcharge f(T&,long)
est sélectionnée, car le type ne correspond pas au paramètre T*
.
Remarque: d'après les remarques du fichier concernant la compatibilité avec Borland, les tableaux ne se désintègrent pas aux pointeurs, mais sont transmis par référence.
Que se passe-t-il dans cette surcharge?
Nous voulons éviter d'appliquer operator&
Au type, car il peut avoir été surchargé.
La norme garantit que reinterpret_cast
Peut être utilisé pour ce travail (voir la réponse de @Matteo Italia: 5.2.10/10).
Boost ajoute quelques subtilités avec les qualificatifs const
et volatile
pour éviter les avertissements du compilateur (et utilisez correctement un const_cast
Pour les supprimer).
T&
Sur char const volatile&
const
et volatile
&
Pour prendre l'adresseT*
Le jonglage const
/volatile
est un peu de magie noire, mais il simplifie le travail (plutôt que de fournir 4 surcharges). Notez que puisque T
n'est pas qualifié, si nous passons un ghost const&
, Alors T*
Est ghost const*
, Donc les qualificatifs n'ont pas vraiment été perdus.
EDIT: la surcharge du pointeur est utilisée pour le pointeur sur les fonctions, j'ai quelque peu modifié l'explication ci-dessus. Je ne comprends toujours pas pourquoi c'est nécessaire cependant.
Ce qui suit sortie idéone résume cela un peu.
Essentiellement, vous pouvez réinterpréter l'objet en tant que référence à char, prendre son adresse (n'appellera pas la surcharge) et reconvertir le pointeur en un pointeur de votre type.
Le code Boost.AddressOf fait exactement cela, en prenant juste soin de la qualification volatile
et const
.
L'astuce derrière boost::addressof
et l'implémentation fournie par @Luc Danton repose sur la magie du reinterpret_cast
; la norme indique explicitement au §5.2.10 ¶10 que
Une expression de valeur l de type
T1
Peut être convertie en type "référence àT2
" Si une expression de type "pointeur versT1
" Peut être explicitement convertie en type " pointeur versT2
"à l'aide d'unreinterpret_cast
. Autrement dit, une conversion de référencereinterpret_cast<T&>(x)
a le même effet que la conversion*reinterpret_cast<T*>(&x)
avec les opérateurs&
Et*
Intégrés. Le résultat est une lvalue qui fait référence au même objet que la lvalue source, mais avec un type différent.
Maintenant, cela nous permet de convertir une référence d'objet arbitraire en un char &
(Avec une qualification cv si la référence est qualifiée cv), car tout pointeur peut être converti en (éventuellement qualifié cv) char *
. Maintenant que nous avons un char &
, La surcharge de l'opérateur sur l'objet n'est plus pertinente, et nous pouvons obtenir l'adresse avec l'opérateur &
Intégré.
L'implémentation de boost ajoute quelques étapes pour travailler avec des objets qualifiés par cv: le premier reinterpret_cast
Est fait pour const volatile char &
, Sinon une distribution simple de char &
Ne fonctionnerait pas pour const
et/ou volatile
références (reinterpret_cast
ne peut pas supprimer const
). Ensuite, le const
et le volatile
sont supprimés avec const_cast
, L'adresse est prise avec &
, Et un dernier reinterpet_cast
Au "correct" "le type est terminé.
Le const_cast
Est nécessaire pour supprimer le const
/volatile
qui aurait pu être ajouté à des références non const/volatiles, mais cela ne "nuit" pas à ce qui était un const
/volatile
référence en premier lieu, car le reinterpret_cast
final ajoutera de nouveau la qualification cv si elle était là en premier lieu (reinterpret_cast
ne peut pas supprimer le const
mais peut l'ajouter).
En ce qui concerne le reste du code dans addressof.hpp
, Il semble que la plupart soit destiné à des solutions de contournement. La static inline T * f( T * v, int )
semble être nécessaire uniquement pour le compilateur Borland, mais sa présence introduit le besoin de addr_impl_ref
, Sinon les types de pointeurs seraient interceptés par cette deuxième surcharge.
Edit: les différentes surcharges ont une fonction différente, voir @ Matthieu M. excellente réponse =.
Eh bien, je n'en suis plus sûr non plus; Je devrais approfondir ce code, mais maintenant je prépare le dîner :), je vais y jeter un œil plus tard.
J'ai vu une implémentation de addressof
faire ceci:
char* start = &reinterpret_cast<char&>(clyde);
ghost* pointer_to_clyde = reinterpret_cast<ghost*>(start);
Ne me demandez pas à quel point c'est conforme!
Jetez un œil à boost :: addressof et à sa mise en œuvre.