web-dev-qa-db-fra.com

Accès au membre protégé via membre-pointeur: est-ce un hack?

Nous savons tous que les membres spécifiés protected à partir d'une classe de base sont uniquement accessibles à partir d'une instance propre à une classe dérivée. Ceci est une fonctionnalité de la norme, et cela a été discuté à plusieurs reprises sur le dépassement de pile:

Mais il semble possible de contourner cette restriction avec des pointeurs membres, en tant qu'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émo en direct sur colir

Pourquoi est-ce possible, s'agit-il d'une fonctionnalité recherchée ou d'un problème quelque part dans la mise en œuvre ou le libellé de la norme?


Des commentaires ont émergé une autre question: si Derived::f est appelé avec un Base réel, est-ce un comportement indéfini?

52
YSC

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

L'expression &Derived::value(dont le type est int Base::*) est parfaitement conforme aux normes et désigne le membre value de Base. Ensuite, l'expression a_base.*pp est un pointeur sur un membre de Base et a_base Une instance de Base vaut également conforme aux normes .

Ainsi, tout compilateur conforme aux normes doit rendre l'expression other.*(&Derived::value); comportement défini: accéder au membre value de other.

30
Oliv

est-ce un hack?

De manière similaire à l'utilisation de reinterpret_cast, cela peut être dangereux et potentiellement source de bogues difficiles à trouver. Mais il est bien formé et il ne fait aucun doute 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 UB. Mais reinterpret_cast contourne le système de types et le système de types existe pour une raison. De même, le pointeur sur membre astuce est bien formé selon le standard, mais il contourne l’encapsulation des membres, et cette encapsulation (typiquement) existe pour une raison (je dis typiquement, puisqu’un programmeur pouvant utiliser l’encapsulation de manière frivole).

Est-ce un problème quelque part dans la mise en œuvre ou le libellé de la norme?

Non, l'implémentation est correcte. Voici comment la langue a été spécifiée pour fonctionner.

La 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 sur un membre de Base. Ceci peut être appliqué à une référence à Base. Les privilèges d'accès des membres ne s'appliquent pas aux pointeurs sur des membres: ils s'appliquent uniquement aux noms des membres.


Des commentaires ont émergé une autre question: si Derived :: f est appelé avec une Base réelle, s'agit-il d'un comportement indéfini?

Pas UB. Base a le membre.

15
eerorika