web-dev-qa-db-fra.com

Pourquoi une méthode const publique n'est-elle pas appelée alors que la méthode non-const est privée?

Considérons ce code:

struct A
{
    void foo() const
    {
        std::cout << "const" << std::endl;
    }

    private:

        void foo()
        {
            std::cout << "non - const" << std::endl;
        }
};

int main()
{
    A a;
    a.foo();
}

L'erreur du compilateur est:

erreur: 'void A :: foo ()' est privé`.

Mais lorsque je supprime la version privée, cela fonctionne. Pourquoi la méthode publique const n'est-elle pas appelée alors que la méthode non-const est privée?

En d'autres termes, pourquoi la résolution de surcharge vient-elle avant le contrôle d'accès? Cela est étrange. Pensez-vous que c'est cohérent? Mon code fonctionne, puis j'ajoute une méthode et mon code de travail ne compile pas du tout.

113
Narek

Lorsque vous appelez a.foo();, le compilateur passe par la résolution de surcharge pour trouver la meilleure fonction à utiliser. Quand il construit le jeu de surcharge, il trouve

void foo() const

et

void foo()

Maintenant, puisque a n’est pas const, la version non-constante est la meilleure correspondance. Le compilateur choisit donc void foo(). Ensuite, les restrictions d’accès sont mises en place et vous obtenez une erreur de compilation, puisque void foo() est privé.

N'oubliez pas qu'en résolution de surcharge, il ne s'agit pas de "trouver la meilleure fonction utilisable". Il s'agit de "trouver la meilleure fonction et essayer de l'utiliser". S'il ne le peut pas à cause de restrictions d'accès ou d'être supprimé, vous obtenez une erreur de compilation.

En d'autres termes, pourquoi la résolution de surcharge vient-elle avant le contrôle d'accès?

Eh bien, regardons:

struct Base
{
    void foo() { std::cout << "Base\n"; }
};

struct Derived : Base
{
    void foo() { std::cout << "Derived\n"; }
};

struct Foo
{
    void foo(Base * b) { b->foo(); }
private:
    void foo(Derived * d) { d->foo(); }
};

int main()
{
    Derived d;
    Foo f;
    f.foo(&d);
}

Maintenant, disons que je ne voulais pas réellement rendre void foo(Derived * d) privé. Si le contrôle d'accès venait en premier, ce programme serait compilé et exécuté et Base serait imprimé. Cela pourrait être très difficile à localiser dans une base de code volumineuse. Comme le contrôle d’accès intervient après la résolution de la surcharge, une erreur du compilateur Nice me dit que la fonction que je souhaite appeler ne peut pas être appelée et que le bogue est beaucoup plus facile à trouver.

126
NathanOliver

En fin de compte, cela revient à l’affirmation de la norme selon laquelle l’accessibilité ne doit pas être prise en compte lors de la résolution de surcharge . Cette assertion peut être trouvée dans [over.match] clause 3:

... Lorsque la résolution de surcharge réussit et que la meilleure fonction viable n'est pas accessible (Clause [class.access]) dans le contexte dans lequel elle est utilisée, le programme est mal formé.

et aussi le Remarque dans la clause 1 de la même section:

[Remarque: la fonction sélectionnée par la résolution de surcharge n'est pas garantie pour le contexte. D'autres restrictions, telles que l'accessibilité de la fonction, peuvent nuire à son utilisation dans le contexte de l'appel. - note de fin]

Pour ce qui est de pourquoi, je peux penser à plusieurs motivations possibles:

  1. Il empêche les changements de comportement inattendus résultant de la modification de l'accessibilité d'un candidat à la surcharge (à la place, une erreur de compilation se produira).
  2. Il supprime la dépendance du contexte du processus de résolution de surcharge (c'est-à-dire que la résolution de surcharge aurait le même résultat, que ce soit à l'intérieur ou à l'extérieur de la classe).
35
atkins

Supposons que le contrôle d'accès vienne avant la résolution de la surcharge. En pratique, cela voudrait dire que public/protected/private Contrôlait la visibilité plutôt que l'accessibilité.

La section 2.10 de Conception et évolution de C++ par Stroustrup contient un passage où il discute de l'exemple suivant.

int a; // global a

class X {
private:
    int a; // member X::a
};

class XX : public X {
    void f() { a = 1; } // which a?
};

Stroustrup mentionne que l’un des avantages des règles actuelles (visibilité avant accessibilité) est que le fait de modifier (temporairement) le private à l’intérieur de class X En public (par exemple à des fins de débogage) est: qu'il n'y a pas de changement discret dans la signification du programme ci-dessus (c'est-à-dire que l'on tente d'accéder à X::a dans les deux cas, ce qui donne une erreur d'accès dans l'exemple ci-dessus). Si public/protected/private Contrôlait la visibilité, la signification du programme serait modifiée (global a serait appelé avec private, sinon X::a).

Il déclare ensuite qu'il ne se souvient pas s'il s'agissait d'une conception explicite ou d'un effet secondaire de la technologie de préprocesseur utilisée pour implémenter le prédécesseur C with Classess au standard C++.

Comment est-ce lié à votre exemple? Fondamentalement, parce que la résolution de surcharge définie par Standard est conforme à la règle générale, la recherche de nom vient avant le contrôle d'accès.

10.2 Recherche de nom de membre [class.member.lookup]

1 La recherche de nom de membre détermine la signification d'un nom (id-expression) dans la portée d'une classe (3.3.7). La recherche de nom peut créer une ambiguïté, auquel cas le programme est mal formé. Pour une expression id, la recherche de nom commence dans la classe de cette classe; pour un identifiant qualifié, la recherche de nom commence dans la portée du spécificateur de nom imbriqué. La recherche de nom a lieu avant le contrôle d'accès (3.4, article 11).

8 Si le nom d'une fonction surchargée est trouvé sans ambiguïté, la résolution de la surcharge (13.3) a également lieu avant le contrôle d'accès . Les ambiguïtés peuvent souvent être résolues en qualifiant un nom avec son nom de classe.

31
TemplateRex

Puisque le pointeur implicite this est non -const, le compilateur vérifiera d'abord la présence d'une version non -const de la fonction avant un const version.

Si vous marquez explicitement le non -const one private, la résolution échouera et le compilateur ne poursuivra pas la recherche.

23
Bathsheba

Il est important de garder à l’esprit l’ordre des événements, à savoir:

  1. Trouvez toutes les fonctions viables.
  2. Choisissez la meilleure fonction viable.
  3. S'il n'y a pas exactement une meilleure solution viable ou si vous ne pouvez pas appeler la meilleure fonction viable (en raison de violations d'accès ou de la fonction deleted), échouez.

(3) se produit après (2). Ce qui est vraiment important, car sinon, créer des fonctions deleted ou private deviendrait en quelque sorte dépourvu de sens et beaucoup plus difficile à raisonner.

Dans ce cas:

  1. Les fonctions viables sont A::foo() et A::foo() const.
  2. La meilleure fonction viable est A::foo(), car cette dernière implique une conversion de qualification sur l'argument implicite this.
  3. Mais A::foo() est private et vous n'y avez pas accès, donc le code est mal formé.
20
Barry

Cela revient à une décision de conception assez élémentaire en C++.

En recherchant la fonction pour satisfaire un appel, le compilateur effectue une recherche comme celle-ci:

  1. Il cherche à trouver le premier1 portée à laquelle il y a quelque chose avec ce nom.

  2. Le compilateur trouve tout les fonctions (ou les foncteurs, etc.) portant ce nom dans cette étendue.

  3. Ensuite, le compilateur surcharge la résolution pour trouver le meilleur candidat parmi ceux qu’il a trouvés (qu’ils soient accessibles ou non).

  4. Enfin, le compilateur vérifie si la fonction choisie est accessible.

En raison de cet ordre, oui, il est possible que le compilateur choisisse une surcharge qui n’est pas accessible, même s’il existe une autre surcharge qui est accessible (mais non choisie lors de la résolution de la surcharge).

Quant à savoir si ce serait possible de faire les choses différemment: oui, c'est sans aucun doute possible. Cela mènerait cependant à un langage très différent de C++. Il s'avère que beaucoup de décisions apparemment mineures peuvent avoir des conséquences qui affectent beaucoup plus que ce qui pourrait être évident au départ.


  1. "First" peut être un peu complexe en lui-même, en particulier lorsque/si des modèles sont impliqués, car ils peuvent conduire à une recherche en deux phases, ce qui signifie qu'il existe deux "racines" entièrement distinctes à partir desquelles effectuer la recherche. L'idée de base est cependant assez simple: commencez par la plus petite portée englobante et dirigez-vous vers des étendues englobantes de plus en plus grandes.
14
Jerry Coffin

Les contrôles d'accès (public, protected, private) n'affectent pas la résolution de surcharge. Le compilateur choisit void foo() parce que c'est la meilleure correspondance. Le fait que ce ne soit pas accessible ne change pas cela. Le supprimer ne laisse que void foo() const, qui est alors la meilleure (c'est-à-dire la seule) correspondance.

12
Pete Becker

Dans cet appel:

a.foo();

Il y a toujours un pointeur implicite this disponible dans chaque fonction membre. Et la qualification const de this est extraite de la référence/de l’appel appelant. L’appel ci-dessus est traité par le compilateur comme:

A::foo(a);

Mais vous avez deux déclarations de A::foo qui est traité comme:

A::foo(A* );
A::foo(A const* );

Par résolution de surcharge, le premier sera sélectionné pour this non-const, le second sera sélectionné pour un const this. Si vous supprimez le premier, le second sera lié à const et non-constthis.

Après la résolution de surcharge pour sélectionner la meilleure fonction viable, vient le contrôle d'accès. Puisque vous avez spécifié l'accès à la surcharge choisie sous la forme private, le compilateur se plaindra alors.

La norme le dit:

[class.access/4] : ... Dans le cas de noms de fonction surchargés, le contrôle d'accès est appliqué au fonction sélectionnée par la résolution de surcharge ....

Mais si vous faites ceci:

A a;
const A& ac = a;
ac.foo();

Ensuite, seule la surcharge const sera corrigée.

11
WhiZTiM

La raison technique a été répondue par d'autres réponses. Je vais me concentrer uniquement sur cette question:

En d'autres termes, pourquoi la résolution de surcharge vient avant le contrôle d'accès? Cela est étrange. Pensez-vous que c'est cohérent? Mon code fonctionne, puis j'ajoute une méthode et mon code de travail ne compile pas du tout.

C'est comme ça que le langage a été conçu. L'intention est d'essayer d'appeler la meilleure surcharge viable, dans la mesure du possible. Si cela échoue, une erreur sera déclenchée pour vous rappeler de revoir la conception.

D'autre part, supposons que votre code soit compilé et fonctionne correctement avec la fonction membre const qui est invoquée. Un jour, quelqu'un (peut-être vous-même) décidera alors de changer l'accessibilité de la fonction membre non -const de private à public. Ensuite, le comportement changerait sans aucune erreur de compilation! Ce serait un surprise.

9
songyuanyao

Les spécificateurs d'accès n'affectent jamais la résolution de recherche de nom ou d'appel de fonction. La fonction est sélectionnée avant que le compilateur vérifie si l'appel doit déclencher une violation d'accès.

Ainsi, si vous modifiez un spécificateur d'accès, vous serez alerté lors de la compilation en cas de violation du code existant. si la confidentialité est prise en compte pour la résolution des appels de fonctions, le comportement de votre programme peut changer silencieusement.

8
Kyle Strand

Parce que la variable a de la fonction main n'est pas déclarée comme const.

Les fonctions membres constantes sont appelées sur des objets constants.

8