53 votes

Accès à un membre protégé par le biais d'un pointeur de membre : est-ce un hack ?

Nous connaissons tous des membres spécifiés protected d'une classe de base ne peuvent être accédées qu'à partir de la propre instance d'une classe dérivée. Il s'agit d'une fonctionnalité de la norme, qui a été discutée à plusieurs reprises sur Stack Overflow :

Mais il semble possible de contourner cette restriction avec des pointeurs membres, comme l'utilisateur chtz m'a montré :

struct Base { protected: int value; };
struct Derived : Base
{
    void f(Base const& other)
    {
        //int n = other.value; // error: 'int Base::value' is protected within this context
        int n = other.*(&Derived::value); // ok??? why?
        (void) n;
    }
};

Démonstration en direct sur coliru

Pourquoi cela est-il possible ? S'agit-il d'une fonctionnalité souhaitée ou d'un problème dans la mise en œuvre ou la formulation de la norme ?


Les commentaires ont fait émerger une autre question : si Derived::f est appelé avec un Base Est-ce un comportement indéfini ?

0 votes

Les commentaires ne sont pas destinés à une discussion approfondie ; cette conversation a été déplacé vers le chat .

2 votes

@YvetteColomb C'était un effort collectif pour trouver une solution à la question/améliorer la question. N'y a-t-il pas moyen de les remettre en place ? Ils contiennent encore des informations qui pourraient améliorer la réponse acceptée.

0 votes

Ils sont tous encore là dans le chat lié.

31voto

Oliv Points 7148

Le fait qu'un membre ne soit pas accessible en utilisant accès aux membres de la classe expr.ref ( aclass.amember ) en raison de contrôle d'accès [class.access] ne rend pas ce membre inaccessible à l'aide d'autres expressions.

L'expression &Derived::value (dont le type est int Base::* ) est parfaitement conforme à la norme, et il désigne le membre value de Base . Alors l'expression a_base.*pp est un pointeur vers un membre de Base et a_base une instance de Base est également conforme à la norme .

Ainsi, tout compilateur conforme à la norme doit rendre l'expression other.*(&Derived::value); comportement défini : accès au membre value de other .

15voto

user2079303 Points 4916

C'est un piratage ?

Dans la même veine que l'utilisation de reinterpret_cast En effet, cela peut être dangereux et peut potentiellement être une source de bogues difficiles à trouver. Mais c'est bien formé et il n'y a aucun doute sur le fait que cela devrait fonctionner.

Pour clarifier l'analogie : Le comportement de reinterpret_cast est également spécifié exactement dans la norme et peut être utilisé sans aucune UB. Mais reinterpret_cast contourne le système de type, et le système de type est là pour une raison. De même, cette astuce du pointeur vers le membre est bien formée selon la norme, mais elle contourne l'encapsulation des membres, et cette encapsulation (typiquement) existe pour une raison (je dis typiquement, car je suppose qu'un programmeur peut utiliser l'encapsulation de manière frivole).

[S'agit-il d'un problème dans la mise en œuvre ou la formulation de la norme ?

Non, l'implémentation est correcte. C'est ainsi que le langage a été spécifié pour fonctionner.

Fonction membre de Derived peut évidemment accéder &Derived::value puisqu'il s'agit d'un membre protégé d'une base.

Le résultat de cette opération est un pointeur vers un membre de l'application Base . Cela peut être appliqué à une référence à Base . Les privilèges d'accès aux membres ne s'appliquent pas aux pointeurs vers les membres : Il ne s'applique qu'aux noms des membres.


Les commentaires ont fait émerger une autre question : si Derived::f est appelé avec une Base réelle, est-ce un comportement non défini ?

Pas UB. Base a le membre.

-1voto

Bert Bril Points 322

Juste pour compléter les réponses et zoomer un peu sur l'horreur que je peux lire entre vos lignes. Si vous considérez les spécificateurs d'accès comme "la loi", qui vous surveille pour vous empêcher de faire de "mauvaises choses", je pense que vous êtes à côté de la plaque. public , protected , private , const ... font tous partie d'un système qui est un énorme plus pour le C++. Les langages qui en sont dépourvus peuvent avoir de nombreux mérites, mais lorsque vous construisez de grands systèmes, ces éléments sont un véritable atout.

Cela dit : Je pense que c'est une bonne chose qu'il soit possible de contourner presque tous les filets de sécurité qui vous sont fournis. Pour autant que vous vous souveniez que "possible" ne signifie pas "bon". C'est pourquoi il ne faut jamais parler de "facilité". Mais pour le reste, c'est vous qui décidez. Vous êtes l'architecte.

Il y a quelques années, je pouvais simplement faire cela (et cela peut encore fonctionner dans certains environnements) :

#define private public

Très utile pour les fichiers d'en-tête externes "hostiles". Bonne pratique ? Qu'en pensez-vous ? Mais parfois vos options sont limitées.

Donc oui, ce que vous montrez est une sorte de brèche dans le système. Mais bon, qu'est-ce qui vous empêche de dériver et de distribuer des références publiques au membre ? Si d'horribles problèmes d'entretien vous excitent - par tous les moyens, pourquoi pas ?

0 votes

Je trouve le premier paragraphe plutôt confus. Qu'est-ce que les spécificateurs d'accès sinon un moyen de "vous empêcher de faire de 'mauvaises choses'" ? Je pense que dans un programme correct, vous pourriez transformer tout ce qui est privé en public et il serait toujours correct.

0 votes

Je considère toutes ces choses comme des outils. La sécurité des types, la correction des constantes, les spécificateurs d'accès - nous savons que nous pouvons nous en passer (choisissez votre langage), mais nous pouvons choisir d'utiliser ces outils. Il y a des avantages et des inconvénients à chaque décision. Ce que vous dites, c'est que tout choix de se déconnecter sélectivement et délibérément sera toujours mauvais ? Avez-vous déjà utilisé un plâtre ?

-2voto

En fait, ce que vous faites, c'est tromper le compilateur, et c'est censé fonctionner. Je vois toujours ce genre de questions et les gens obtiennent parfois de mauvais résultats et parfois cela fonctionne, en fonction de la façon dont cela se convertit en code assembleur.

Je me souviens avoir vu une affaire avec un const sur un entier, mais avec un peu de ruse, le gars a pu changer la valeur et a réussi à contourner l'attention du compilateur. Le résultat a été : Une valeur erronée pour une simple opération mathématique. La raison en est simple : L'assemblage en x86 fait une distinction entre les constantes et les variables, car certaines instructions contiennent des constantes dans leur opcode. Donc, puisque le compilateur croit c'est une constante, il la traitera comme une constante et la traitera de manière optimisée avec la mauvaise instruction du CPU, et baam, vous avez une erreur dans le nombre résultant.

En d'autres termes : Le compilateur va essayer d'appliquer toutes les règles qu'il peut appliquer, mais vous pouvez probablement le tromper, et vous pouvez ou non obtenir des résultats erronés en fonction de ce que vous essayez de faire, donc vous feriez mieux de faire de telles choses seulement si vous savez ce que vous faites.

Dans votre cas, le pointeur &Derived::value peut être calculée à partir d'un objet par le nombre d'octets qu'il y a depuis le début de la classe. C'est essentiellement de cette façon que le compilateur y accède, donc, le compilateur :

  1. Ne voit pas de problème avec les permissions, parce que vous accédez à value par le biais de derived au moment de la compilation.
  2. Peut le faire, parce que vous prenez le décalage en octets dans un objet qui a la même structure que derived (bien, évidemment, le base ).

Donc, vous ne violez aucune règle. Vous avez réussi à contourner les règles de compilation. Vous ne devriez pas le faire, exactement pour les raisons décrites dans les liens que vous avez joints, car cela brise l'encapsulation de la POO, mais, bon, si vous savez ce que vous faites...

3 votes

Je doute que le PO s'interroge sur les aspects pratiques

6 votes

Vaillant effort. Mais c'est une très longue non-réponse. Parce que je pense que l'OP s'interroge sur ce que le officiel Le libellé de ce texte est le suivant.

0 votes

J'ai essayé d'expliquer pourquoi cela fonctionne... J'espère que cela vous aidera.

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