web-dev-qa-db-fra.com

Pourquoi C ++ a-t-il besoin de l'opérateur de résolution de portée?

(Je sais ce que fait l'opérateur de résolution de portée, et comment et quand l'utiliser.)

Pourquoi C++ a-t-il le ::, au lieu d'utiliser l'opérateur . opérateur à cet effet? Java n'a pas d'opérateur séparé et fonctionne très bien. Y a-t-il une différence entre C++ et Java cela signifie que C++ nécessite un opérateur séparé pour être analysable?

Ma seule supposition est que :: est nécessaire pour des raisons de priorité, mais je ne vois pas pourquoi il doit avoir une priorité plus élevée que, disons, .. La seule situation que je peux penser serait que quelque chose comme

a.b::c;

serait analysé comme

a.(b::c);

, mais je ne peux penser à aucune situation dans laquelle une syntaxe comme celle-ci serait de toute façon légale.

Peut-être que c'est juste un cas de "ils font des choses différentes, alors ils pourraient aussi bien paraître différents". Mais cela n'explique pas pourquoi :: a une priorité plus élevée que ..

38
Karu

Pourquoi C++ n'utilise pas . Où il utilise ::, C'est parce que c'est ainsi que le langage est défini. Une raison plausible pourrait être de se référer à l'espace de noms global en utilisant la syntaxe ::a Comme indiqué ci-dessous:

int a = 10;
namespace M
{
    int a = 20;
    namespace N
    {
           int a = 30;
           void f()
           {
              int x = a; //a refers to the name inside N, same as M::N::a
              int y = M::a; //M::a refers to the name inside M
              int z = ::a; //::a refers to the name in the global namespace

              std::cout<< x <<","<< y <<","<< z <<std::endl; //30,20,10
           }
    }
}

Démo en ligne

Je ne sais pas comment Java résout ce problème. Je ne sais même pas si dans Java il y a un espace de noms global. En C #, vous vous référez au nom global en utilisant la syntaxe global::a, ce qui signifie que même C # a l'opérateur ::.


mais je ne peux penser à aucune situation dans laquelle une syntaxe comme celle-ci serait de toute façon légale.

Qui a dit que la syntaxe comme a.b::c N'est pas légale?

Considérez ces classes:

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

struct B : A
{
    void f(int) { std::cout << "B::f(int)" << std::endl; }
};

Maintenant, voyez ceci ( ideone ):

B b;
b.f(10); //ok
b.f();   //error - as the function is hidden

b.f() ne peut pas être appelé comme ça, car la fonction est cachée, et le GCC donne ce message d'erreur:

error: no matching function for call to ‘B::f()’

Pour appeler b.f() (ou plutôt A::f()), vous avez besoin de l'opérateur de résolution de portée:

b.A::f(); //ok - explicitly selecting the hidden function using scope resolution

Démo sur ideone

29
Nawaz

Parce que quelqu'un au sein du comité des normes C++ a pensé que c'était une bonne idée de permettre à ce code de fonctionner:

struct foo
{
  int blah;
};

struct thingy
{
  int data;
};

struct bar : public foo
{
  thingy foo;
};

int main()
{
  bar test;
  test.foo.data = 5;
  test.foo::blah = 10;
  return 0;
}

Fondamentalement, il permet à une variable membre et à un type de classe dérivée d'avoir le même nom. J'ai aucune idée ce que quelqu'un fumait quand il pensait que c'était important. Mais ça y est.

Lorsque le compilateur voit ., il sait que la chose à gauche doit être un objet. Quand il voit ::, il doit s'agir d'un nom ou d'un espace de noms (ou rien, indiquant l'espace de noms global). Voilà comment cela résout cette ambiguïté.

30
Nicol Bolas

Contrairement à Java, C++ a un héritage multiple. Voici un exemple où la résolution de portée du type dont vous parlez devient importante:

#include <iostream>
using namespace std;
struct a
{
    int x;
};
struct b
{
    int x;
};
struct c : public a, public b
{
    ::a a;
    ::b b;
};
int main() {
    c v;
    v.a::x = 5;
    v.a.x = 55;
    v.b::x = 6;
    v.b.x = 66;
    cout << v.a::x << " " << v.b::x << endl;
    cout << v.a.x << " " << v.b.x << endl;
    return 0;
}
9
dasblinkenlight

Pourquoi C++ a-t-il l'opérateur ::, au lieu d'utiliser le. opérateur à cet effet?

La raison est donnée par Stroustrup lui-même:

Dans C with Classes, un point était utilisé pour exprimer l'appartenance à une classe ainsi que pour exprimer la sélection d'un membre d'un objet particulier.

Cela avait été la cause d'une certaine confusion mineure et pouvait également être utilisé pour construire des exemples ambigus. Pour remédier à cela, :: a été introduit pour signifier l'appartenance à une classe et . a été conservé exclusivement pour l'appartenance à l'objet

(Bjarne Stroustrup Une histoire de C++: 1979−1991 page 21 - § 3.3.1)

De plus c'est vrai que

ils font des choses différentes, donc ils pourraient aussi bien paraître différents

en effet

Dans N::m ni N ni m sont des expressions avec des valeurs; N et m sont des noms connus du compilateur et :: effectue une résolution de portée (au moment de la compilation) plutôt qu'une évaluation d'expression. On pourrait imaginer permettre une surcharge de x :: y où x est un objet plutôt qu'un espace de noms ou une classe, mais cela impliquerait - contrairement aux premières apparences - l'introduction d'une nouvelle syntaxe (pour permettre à expr::expr). Les avantages d'une telle complication ne sont pas évidents.

Opérateur . (point) pourrait en principe être surchargé en utilisant la même technique que celle utilisée pour ->.

(Bjarne Stroustrup's FAQ sur le style et la technique C++ )

8
manlio

Pour répondre à la dernière partie de la question sur la priorité des opérateurs:

class A {
public:
  char A;
};

class B : public A {
public:
  double A;
};

int main(int c, char** v)
{
  B myB;
  myB.A = 7.89;
  myB.A::A = 'a';
  // On the line above a hypothetical myB.A.A
  // syntax would parse as (myB.A).A and since
  // (myB.A) is of type double you get (double).A in the
  // next step. Of course the '.' operator has no
  // meaning for doubles so it causes a syntax error. 
  // For this reason a different operator that binds
  // more strongly than '.' is needed.
  return 0;
}
3
nolandda

J'ai toujours supposé que l'utilisation de C++ dot/:: était un choix de style, pour rendre le code plus facile à lire. Comme l'écrit l'OP "ils font des choses différentes, ils devraient donc être différents".

Venant de C++, il y a longtemps, en C #, j'ai trouvé que l'utilisation de points était confuse. J'avais l'habitude de voir A::doStuff();B.doStuff();, et de savoir que la première est une fonction régulière, dans un espace de noms, et la seconde est une fonction membre sur l'instance B.

C++ est peut-être mon cinquième langage, après Basic, Assembly, Pascal et Fortran, donc je ne pense pas que ce soit le syndrome du premier langage, et je suis plus un programmeur C # maintenant. Mais, à mon humble avis, si vous avez utilisé les deux, le double-point de style C++ pour les espaces de noms se lit mieux. J'ai l'impression que Java/C # a choisi des points pour les deux pour atténuer (avec succès) l'avant de la courbe d'apprentissage.

2
Owen Reynolds

L'opérateur de résolution d'étendue (: :) est utilisé pour définir une fonction en dehors d'une classe ou lorsque nous voulons utiliser une variable globale mais a également une variable locale du même nom.

1
Vishal Parekh