Почему класс класса С++ нуждается в прямом объявлении только в других пространствах имен?
Предположим, что у меня есть класс F
, который должен быть знаком классам G
(в глобальном пространстве имен) и C
(в пространстве имен A
).
- чтобы быть другом
A::C
, F
должен быть объявлен вперед.
- чтобы быть другом
G
, не требуется форвардное объявление F
.
- класс
A::BF
может быть другом A::C
без прямого объявления
Следующий код иллюстрирует это и компилируется с помощью GCC 4.5, VС++ 10 и, по крайней мере, с другим компилятором.
class G {
friend class F;
int g;
};
// without this forward declaration, F can't be friend to A::C
class F;
namespace A {
class C {
friend class ::F;
friend class BF;
int c;
};
class BF {
public:
BF() { c.c = 2; }
private:
C c;
};
} // namespace A
class F {
public:
F() { g.g = 3; c.c = 2; }
private:
G g;
A::C c;
};
int main()
{
F f;
}
Мне это кажется непоследовательным. Есть ли причина для этого или это просто дизайнерское решение стандарта?
Ответы
Ответ 1
C++
Стандарт ISO/IEC 14882:2003(E)
7.3.1.2 Определения членов пространства имен
Пункт 3
Каждое имя, впервые объявленное в namespace является членом этого Пространство имен. Если объявление друга в нелокальный класс сначала объявляет класс или функция (это означает, что имя класса или функции является неквалифицированным) класс друга или функция является членом внутреннее пространство имен.
// Assume f and g have not yet been defined.
void h(int);
template <class T> void f2(T);
namespace A {
class X {
friend void f(X); // A::f(X) is a friend
class Y {
friend void g(); // A::g is a friend
friend void h(int); // A::h is a friend
// ::h not considered
friend void f2<>(int); // ::f2<>(int) is a friend
};
};
// A::f, A::g and A::h are not visible here
X x;
void g() { f(x); } // definition of A::g
void f(X) { /* ... */} // definition of A::f
void h(int) { /* ... */ } // definition of A::h
// A::f, A::g and A::h are visible here and known to be friends
}
Ваш friend class BF;
является объявлением A::BF
в пространстве имен A, а не в глобальном пространстве имен. Чтобы избежать этого нового объявления, вам потребуется глобальное предварительное объявление.
Ответ 2
Учесть эти 3 строки кода из вашего примера:
1. friend class F; // it creates "friend declaration", (that not the same as ordinary forward declaration
2. class F; // without this forward declaration, F can't be friend to A::C <-- this is ordinary forward declaration
3. friend class ::F; // this is qualified lookup (because of ::), so it needs previous declaration, which you provide in line 2.
Стандарт С++ в пункте 7.3.1.2, пункт 3 (определения членов пространства имен) говорит:
Объявление друга само по себе не делает имя видимым безоговорочный поиск (3.4.1) или квалифицированный поиск (3.4.3). [ Обратите внимание имя друга будет отображаться в его пространстве имен, если совпадение декларация предоставляется в области пространства имен (до или после определение класса, предоставляющее дружбу). -end note]
И строка 2 точно соответствует требованиям стандарта.
Вся путаница заключается в том, что "объявление друга" слабое, вам нужно предоставить твердую декларацию для дальнейшего использования.
Ответ 3
Потому что было бы бессмысленно объявлять что-то в глобальном пространстве имен, если вы находитесь внутри блока namespace {}
. Причина friend class BF;
заключается в том, что она действует как неявное декларативное объявление.