为什么C++友元类只需要在其他命名空间中进行前向声明?

57
假设我有一个名为 F 的类,它应该是类 G(在全局命名空间)和类 C(在命名空间 A 中)的友元。
  • 要成为 A::C 的友元,必须提前声明 F
  • 要成为 G 的友元,不需要提前声明 F
  • 同样,类 A::BF 可以成为 A::C 的友元而无需提前声明。
下面的代码说明了这一点,并使用 GCC 4.5、VC++ 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;
}

对我来说,这似乎是不一致的。这是有原因的还是只是标准的设计决定?
3个回答

54

C++标准ISO/IEC 14882:2003(E)

7.3.1.2 命名空间成员定义

第3段

Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class or function (this implies that the name of the class or function is unqualified) the friend class or function is a member of the innermost enclosing 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 中声明 A::BF 而不是全局命名空间。你需要在此新声明之前进行全局先前声明。


20
一个理性的人可能会问,为什么不只写"friend class ::F"(如OP代码中所示),从而明确将F推入全局命名空间。我认为答案是“限定符ID从未声明新名称”,但不确定标准对此问题的具体规定。 - zwol
1
@Zack 那也是我的最初问题:当我将一个类移动到另一个命名空间时,为什么需要添加前向声明?但是已经有一些关于这个主题的讨论了(例如https://dev59.com/83I95IYBdhLWcg3w_zSs,https://dev59.com/hnM_5IYBdhLWcg3wdy5g)。看起来在相同作用域中不需要前向声明是标准作者授予的方便。 - pesche
所有这些C++标准都很酷,但是微软带着它的MSVC编译器来了,即使启用了符合模式,你只需要写 friend class F 而不需要 :: ,甚至不需要提前声明 class F; 它仍然会编译通过 ¯_(ツ)_/¯ - Kyriet
所有这些C++标准都很酷,但是微软的MSVC编译器却出现了,即使启用了符合模式,你只需写friend class F而不需要::,甚至不需要提前声明class F;它也会编译通过 ¯_(ツ)_/¯ - undefined

7

让我们来看一下你样例中的这三行代码:

1. friend class F; // it creates "friend declaration", (that's 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.

C++标准中第7.3.1.2段,第3点(命名空间成员定义)的规定如下:友元声明本身不会使名称在未限定查找(3.4.1)或限定查找(3.4.3)中可见。[注意:如果在命名空间范围内(在授予友元类定义之前或之后)提供了匹配的声明,则友元的名称将在其命名空间中可见。--end 注意]。第2行正是按照标准要求执行的。 所有的混淆都是因为"friend declaration"很弱,您需要提供坚实的前向声明以供进一步使用。

2

如果您在namespace {}块中,声明全局命名空间中的内容将没有意义。 friend class BF;之所以有效,是因为它类似于隐式前向声明。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接