173 votes

Comment puis-je obtenir l'adresse d'un objet de manière fiable?

Considérons 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 puis-je obtenir de l' clydes'adresse?

Je suis à la recherche d'une solution qui fonctionne tout aussi bien pour tous les types d'objets. En C++03 solution serait sympa, mais je suis intéressé par le C++11 solutions de trop. Si possible, il faut éviter toute mise en œuvre spécifique de comportement.

Je suis conscient de C++11 std::addressof modèle de fonction, mais je ne suis pas intéressé à l'utiliser ici: j'aimerais comprendre comment une Bibliothèque Standard réalisateur pourrait mettre en œuvre ce modèle de fonction.

105voto

Konrad Rudolph Points 231505

Essentiellement, vous pouvez réinterpréter l'objet comme référence-à-caractère, prendre son adresse (ne pas appeler la surcharge) et renvoyer le pointeur vers un pointeur de votre type.

Le code Boost.AddressOf fait exactement cela, prenant juste soin de volatile et const qualification.

102voto

Matthieu M. Points 101624

Laissez-nous d'abord copier le code de Boost, moins le compilateur de travail autour de 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 );
}

Ce qui se passe si on passe une référence à la fonction ?

Remarque: addressof ne peut pas être utilisé avec un pointeur de fonction

En C++, si void func(); est déclarée, alors func est une référence à une fonction ne prenant aucun argument et renvoie aucun résultat. Cette référence à une fonction peut être trivialement converti en un pointeur de fonction-d' @Konstantin: Selon 13.3.3.2 les deux T & et T * sont indiscernables pour les fonctions. Le 1er est une Identité de conversion et le 2ème est la Fonction de Pointeur de la conversion à la fois d'avoir des "Correspondance Exacte" rang (13.3.3.1.1 tableau 9).

La référence à la fonction pass through addr_impl_ref, il y a une ambiguïté dans la résolution de surcharge pour le choix de l' f, ce qui est résolu grâce à l'argument factice 0, ce qui est un int première et pourrait être promu à un long (Promotion Intégrale).

Ainsi, nous renvoie simplement le pointeur.

Qu'advient-il si l'on fait passer d'un type avec un opérateur de conversion ?

Si l'opérateur de conversion donne un T* puis nous avons une ambiguïté: pour f(T&,long) d'une Promotion Intégrale est nécessaire pour le deuxième argument, tandis que pour l' f(T*,int) l'opérateur de conversion est appelée sur la première (merci à @litb)

C'est lors de l' addr_impl_ref coups de pied dans. La Norme C++ mandats qu'une conversion de la séquence peut contenir au plus une conversion définie par l'utilisateur. En enveloppant le type en addr_impl_ref et de forcer l'utilisation d'une conversion d'une séquence déjà, nous "désactiver" tout opérateur de conversion que le type qui vient avec.

Ainsi, l' f(T&,long) de surcharge est activée (et à la Promotion Intégrale réalisée).

Ce qui se passe pour tout autre type ?

Ainsi, l' f(T&,long) de surcharge est activée, parce que là, le type ne correspond pas à l' T* paramètre.

Note: le commentaire dans le fichier concernant Borland compatibilité, les tableaux ne baissent pas pour les pointeurs, mais sont passés par référence.

Ce qui se passe dans cette surcharge ?

Nous voulons éviter d'appliquer des operator& pour le type, comme il a pu être surchargé.

La Norme garantit reinterpret_cast peut être utilisé pour ce travail (voir @Matteo Italia réponse: 5.2.10/10).

Boost ajoute quelques subtilités avec const et volatile qualificatifs pour éviter les avertissements du compilateur (et utiliser correctement un const_cast pour les supprimer).

  • Cast T& de char const volatile&
  • Stip l' const et volatile
  • Appliquer l' & de l'opérateur de prendre l'adresse
  • Cast retour à un T*

L' const/volatile jonglerie est un peu de la magie noire, mais il ne simplifient le travail (plutôt que de fournir des 4 surcharges). A noter que depuis T non qualifié est, si l'on fait passer un ghost const&, alors T* est ghost const*, donc les qualificatifs n'ont pas vraiment été perdu.

EDIT: le pointeur de la surcharge est utilisé pour le pointeur de fonctions, j'ai modifié l'explication ci-dessus un peu. Je ne comprends toujours pas pourquoi il est nécessaire de bien.

La suite ideone sortie résume bien la situation, un peu.

49voto

Matteo Italia Points 53117

Le truc derrière boost::addressof et la mise en œuvre fournis par @Luc Danton s'appuie sur la magie de l' reinterpret_cast; la norme stipule explicitement au §5.2.10 ¶10

Une lvalue expression de type T1 peut être converti dans le type "référence à l' T2" si une expression de type "pointeur vers T1" peut être explicitement converties dans le type "pointeur vers T2" à l'aide d'un reinterpret_cast. C'est, une référence cast reinterpret_cast<T&>(x) a le même effet que la conversion *reinterpret_cast<T*>(&x) avec le haut- & et * opérateurs. Le résultat est une lvalue, qui désigne le même objet que la source lvalue, mais avec un autre type.

Maintenant, cela nous permet de convertir un objet arbitraire référence à un char & (avec un cv de qualification si la référence cv-qualifié), parce que les pointeurs peuvent être convertis à une (peut-être de cv qualifiés) char *. Maintenant que nous avons un char &, la surcharge d'opérateur sur l'objet n'est plus pertinente, et nous pouvons obtenir l'adresse avec le groupe builtin & de l'opérateur.

Le coup de pouce de la mise en œuvre ajoute quelques étapes de travailler avec des cv qualifiés d'objets: le premier reinterpret_cast est fait pour const volatile char &, sinon un simple char & cast ne serait pas travailler pour const et/ou volatile références (reinterpret_cast ne peut pas supprimer const). Puis l' const et volatile est supprimé avec const_cast, l'adresse est pris avec de l' &, et un final reinterpet_cast pour le "corriger" le type est fait.

L' const_cast est nécessaire pour enlever l' const/volatile qui pourrait avoir été ajouté à la non-const/volatils références, mais il n'est pas "mal" ce qui était une const/volatile référence en premier lieu, parce que le final reinterpret_cast re-ajouter le cv-qualification si elle était là en premier lieu (reinterpret_cast ne peut pas supprimer l' const mais peut l'ajouter).

Comme pour le reste du code en addressof.hpp, il semble que la plupart de c'est pour des solutions de contournement. L' static inline T * f( T * v, int ) semble être nécessaire que pour le compilateur Borland, mais sa présence introduit la nécessité d' addr_impl_ref, sinon les types pointeur serait pris par ce deuxième cas de surcharge.

Edit: les différentes surcharges ont une fonction différente, voir @Matthieu M. excellente réponse.

Eh bien, je ne suis plus sûr que ce soit; je devrais continuer à enquêter sur ce code, mais maintenant, je fais cuire le dîner :) , je vais avoir un coup d'oeil plus tard.

12voto

Luc Danton Points 21421

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!

5voto

Jetez un oeil à boost :: addressof et à son implémentation.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X