Подтвердить что ты не робот

Почему С++ нужен оператор разрешения области?

(Я знаю, что делает оператор разрешения области видимости, и как и когда его использовать.)

Почему С++ имеет оператор :: вместо использования оператора . для этой цели? Java не имеет отдельного оператора и отлично работает. Есть ли какая-то разница между С++ и Java, что означает, что С++ требует отдельного оператора для анализа?

Мое единственное предположение: :: необходим для приоритетов, но я не могу понять, почему он должен иметь более высокий приоритет, чем, скажем, .. Единственная ситуация, я могу думать, что это будет так, что что-то вроде

a.b::c;

будет анализироваться как

a.(b::c);

но я не могу думать о какой-либо ситуации, в которой синтаксис вроде бы был бы законным в любом случае.

Может быть, это просто случай "они делают разные вещи, поэтому они могут выглядеть иначе". Но это не объясняет, почему :: имеет более высокий приоритет, чем ..

4b9b3361

Ответ 1

Почему С++ не использует ., где он использует ::, потому что именно так определяется язык. Возможной причиной может быть ссылка на глобальное пространство имен с использованием синтаксиса ::a, как показано ниже:

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

Онлайн-демонстрация

Я не знаю, как Java решает это. Я даже не знаю, есть ли в Java глобальное пространство имен. В С# вы ссылаетесь на глобальное имя, используя синтаксис global::a, что означает, что даже С# имеет оператор ::.


но я не могу придумать никакой ситуации, в которой синтаксис, подобный этому, будет законным в любом случае.

Кто сказал, что синтаксис вроде a.b::c не является законным?

Рассмотрим эти классы:

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

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

Теперь посмотрим это (ideone):

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

b.f() не может быть вызван так, как функция скрыта, и GCC дает это сообщение об ошибке:

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

Чтобы вызвать b.f() (или, скорее, A::f()), вам нужен оператор разрешения области видимости:

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

Демо на идее

Ответ 2

Потому что кто-то в комитете по стандартам С++ подумал, что было бы неплохо позволить этому коду работать:

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

В принципе, он позволяет иметь переменную-член и тип производного класса с тем же именем. Я понятия не имею, что кто-то курил, когда они думали, что это важно. Но вот оно.

Когда компилятор видит ., он знает, что вещь слева должна быть объектом. Когда он видит ::, он должен быть typename или namespace (или ничего, что указывает на глобальное пространство имен). То, как это устраняет эту двусмысленность.

Ответ 3

В отличие от Java, С++ имеет множественное наследование. Вот один из примеров, где важное значение имеет разрешение области видимости, о которой вы говорите:

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

Ответ 4

Просто чтобы ответить на окончательный бит вопроса о приоритете оператора:

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

Ответ 5

Почему у С++ есть оператор:: вместо использования. оператора для этой цели?

Причина дается самим Страуступом:

В C с классами точка использовалась для выражения членства в классе, а также для выражения выбора члена определенного объекта.

Это было причиной некоторой незначительной путаницы, а также может использоваться для построения неоднозначных примеров. Чтобы облегчить это, было введено :: для обозначения членства в классе и . было сохранено исключительно для принадлежности к объекту

(Bjarne Stroustrup История С++: 1979-1991 стр. 21 - § 3.3.1)

Кроме того, верно, что

они делают разные вещи, поэтому они могут выглядеть иначе

действительно

В N::m ни N, ни m не являются выражениями со значениями; N и m являются именами, известными компилятору, а :: выполняет разрешение области (время компиляции), а не оценку выражения. Можно представить себе возможность перегрузки x:: y, где x - это объект, а не пространство имен или класс, но это, вопреки первым появлениям, включает введение нового синтаксиса (чтобы разрешить expr::expr). Неясно, какие выгоды принесет такое осложнение.

Оператор . (точка) в принципе может быть перегружен с использованием той же методики, что и для ->.

(Bjarne Stroustrup Часто задаваемые вопросы по стилю и технике С++)

Ответ 6

Я всегда предполагал, что использование С++ dot/:: use было выбором стиля, чтобы сделать код более удобным для чтения. Как пишет OP, "они делают разные вещи, поэтому должны выглядеть по-другому".

Начиная с С++, давным-давно, до С#, я нашел использование пустых строк. Я привык видеть A::doStuff(); B.doStuff();, и знать, что первая является регулярной функцией, в пространстве имен, а вторая является функцией-членом в экземпляре B.

С++ - это, может быть, мой пятый язык, после Basic, Assembly, Pascal и Fortran, поэтому я не думаю, что это синдром первого языка, и теперь я больше программист на С#. Но, ИМХО, если вы использовали оба варианта, двойная двоеточие типа С++ для пространств имен лучше читается. Я чувствую, что Java/С# выбрали точки для того, чтобы (успешно) облегчить фронт кривой обучения.

Ответ 7

Оператор разрешения масштаба (::) используется для определения функции вне класса или когда мы хотим использовать глобальную переменную, но также имеет локальную переменную с тем же именем.