Почему С++ нужен оператор разрешения области?
(Я знаю, что делает оператор разрешения области видимости, и как и когда его использовать.)
Почему С++ имеет оператор ::
вместо использования оператора .
для этой цели? Java не имеет отдельного оператора и отлично работает. Есть ли какая-то разница между С++ и Java, что означает, что С++ требует отдельного оператора для анализа?
Мое единственное предположение: ::
необходим для приоритетов, но я не могу понять, почему он должен иметь более высокий приоритет, чем, скажем, .
. Единственная ситуация, я могу думать, что это будет так, что что-то вроде
a.b::c;
будет анализироваться как
a.(b::c);
но я не могу думать о какой-либо ситуации, в которой синтаксис вроде бы был бы законным в любом случае.
Может быть, это просто случай "они делают разные вещи, поэтому они могут выглядеть иначе". Но это не объясняет, почему ::
имеет более высокий приоритет, чем .
.
Ответы
Ответ 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
Оператор разрешения масштаба (::) используется для определения функции вне класса или когда мы хотим использовать глобальную переменную, но также имеет локальную переменную с тем же именем.