122 votes

Pourquoi une méthode const publique n'est-elle pas appelée lorsque celle qui n'est pas const est privée ?

Considérez 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 :

error: 'void A::foo()' is private`.

Mais quand je supprime la méthode privée, ça marche. Pourquoi la méthode publique const n'est pas appelée lorsque la non-const est privée ?

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

3 votes

En C++, sans effort supplémentaire comme l'utilisation de l'idiome PIMPL, il n'existe pas de véritable partie "privée" de la classe. C'est juste l'un des problèmes (ajouter une surcharge de méthode "privée" et casser la compilation du code ancien compte comme un problème à mes yeux, même si celui-ci est trivial à éviter en ne le faisant tout simplement pas) causés par cela.

0 votes

Est-ce qu'il existe un code du monde réel où l'on s'attend à pouvoir appeler une fonction constante mais que sa contrepartie non-constante serait partie de l'interface privée ? Cela me semble être une mauvaise conception d'interface.

126voto

NathanOliver Points 10062

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

void foo() const

et

void foo()

Maintenant, puisque a n'est pas const, la version non-const est la meilleure correspondance, alors le compilateur choisit void foo(). Ensuite, les restrictions d'accès sont mises en place et vous obtenez une erreur du compilateur, car void foo() est privée.

N'oubliez pas, dans la 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'. Si cela ne peut pas se faire en raison de restrictions d'accès ou d'une suppression, alors vous obtenez une erreur du compilateur.

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

Eh bien, examinons :

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);
}

Imaginons maintenant que je n'avais pas réellement l'intention de rendre void foo(Derived * d) privée. Si le contrôle d'accès passait en premier, alors ce programme se compilerait et s'exécuterait et Base serait imprimé. Cela pourrait être très difficile à repérer dans une grande base de code. Comme le contrôle d'accès vient après la résolution de surcharge, j'obtiens une belle erreur du compilateur me disant que la fonction que je veux appeler ne peut pas être appelée, et je peux trouver le bug beaucoup plus facilement.

0 votes

Y a-t-il une raison pour laquelle le contrôle d'accès est après la résolution de surcharge?

3 votes

@drake7707 Comme je le montre dans mon exemple de code, si le contrôle d'accès était placé en premier, alors le code ci-dessus compilerait, ce qui modifierait la sémantique du programme. Je ne sais pas pour vous, mais personnellement, je préférerais avoir une erreur et devoir faire une conversion explicite si je voulais que la fonction reste privée, plutôt qu'une conversion implicite et que le code fonctionne silencieusement.

0 votes

"et que je dois effectuer une conversion explicite si je voulais que la fonction reste privée" - il semble que le vrai problème ici soit les conversions implicites... bien que d'un autre côté, l'idée que vous pouvez également utiliser une classe dérivée implicitement comme la classe de base est une caractéristique définissante du paradigme orienté objet, n'est-ce pas?

37voto

atkins Points 1600

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

... 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 la Note dans la clause 1 de la même section :

[ Note : La fonction sélectionnée par la résolution de surcharge n'est pas garantie d'être appropriée pour le contexte. D'autres restrictions, telles que l'accessibilité de la fonction, peuvent rendre son utilisation dans le contexte d'appel mal formée. — fin de la note ]

Quant aux raisons, je peux penser à quelques motivations possibles :

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

32voto

TemplateRex Points 26447

Supposons que le contrôle d'accès vienne avant la résolution de la surcharge. En effet, cela signifierait que la visibilité était contrôlée par public/protected/private plutôt que l'accessibilité.

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

int a; // a global

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

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

Stroustrup mentionne qu'un avantage des règles actuelles (visibilité avant accessibilité) est que le changement (temporaire) de private à l'intérieur de la class X en public (par exemple, à des fins de débogage) n'entraîne pas de modification silencieuse de la signification du programme ci-dessus (c'est-à-dire que X::a est tenté d'être accédé dans les deux cas, ce qui génère une erreur d'accès dans l'exemple ci-dessus). Si public/protected/private contrôlait la visibilité, la signification du programme changerait (l'appel au a global serait fait avec private, sinon X::a).

Il déclare ensuite qu'il ne se rappelle pas si c'était un choix délibéré ou un effet secondaire de la technologie préprocesseur utilisée pour implémenter le C avec les classes, prédécesseur du C++ standard.

Comment cela est-il lié à votre exemple? Essentiellement, car le Standard a fait en sorte que la résolution de la surcharge se conforme à la règle générale selon laquelle 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 (expression-id) dans un espace de classe (3.3.7). La recherche de nom peut aboutir à une ambiguïté, auquel cas le programme est mal formé. Pour une expression-id, la recherche de nom commence dans l'espace de classe de this; pour un qualified-id, la recherche de nom commence dans l'espace du nestedname-specifier. La recherche de nom a lieu avant le contrôle d'accès (3.4, Clause 11).

8 Si le nom d'une fonction surchargée est trouvé de manière non ambiguë, 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.

23voto

Bathsheba Points 23209

Étant donné que le pointeur implicite this n'est pas const, le compilateur va d'abord vérifier la présence d'une version non const de la fonction avant une version const.

Si vous marquez explicitement la version non const comme private, alors la résolution échouera et le compilateur ne continuera pas la recherche.

0 votes

Penses-tu que c'est cohérent? Mon code fonctionne et puis j'ajoute une méthode et mon code fonctionnel ne compile plus du tout.

0 votes

Je le pense. La résolution de surcharge est intentionnellement pointilleuse. J'ai répondu à une question similaire hier: stackoverflow.com/questions/39023325/…

5 votes

@Narek Je crois que cela fonctionne exactement comme le font les fonctions supprimées dans la résolution de surcharge. Il choisit le meilleur parmi l'ensemble, puis constate qu'il n'est pas disponible, vous obtenez donc une erreur de compilation. Il ne choisit pas la meilleure fonction utilisable, mais la meilleure fonction et essaie ensuite de l'utiliser.

20voto

Barry Points 45207

Il est important de garder à l'esprit l'ordre des choses qui se produisent, qui est le suivant :

  1. Trouver toutes les fonctions viables.
  2. Choisir la meilleure fonction viable.
  3. Si il n'y a pas exactement une meilleure fonction viable, ou si vous ne pouvez pas réellement appeler la meilleure fonction viable (en raison de violations d'accès ou de la fonction étant deleted), échouer.

(3) se produit après (2). Ce qui est vraiment important, car sinon rendre les fonctions deleted ou private deviendrait en quelque sorte sans signification 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 la 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é.

1 votes

On pourrait penser que "viable" inclurait des restrictions d'accès pertinentes. En d'autres termes, il n'est pas "viable" d'appeler une fonction privée depuis l'extérieur de la classe, car elle ne fait pas partie de l'interface publique de cette classe.

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