为啥我不能使用双冒号在命名空间中前向声明一个类?

Posted

技术标签:

【中文标题】为啥我不能使用双冒号在命名空间中前向声明一个类?【英文标题】:Why can't I forward-declare a class in a namespace using double colons?为什么我不能使用双冒号在命名空间中前向声明一个类? 【发布时间】:2011-01-04 19:18:19 【问题描述】:
class Namespace::Class;

为什么我必须这样做?:

namespace Namespace 
    class Class;

使用VC++ 8.0,编译器问题:

错误 C2653:“命名空间”:不是类或命名空间名称

我认为这里的问题是编译器无法判断Namespace 是类还是命名空间?但是为什么这很重要,因为它只是一个前向声明?

还有其他方法可以前向声明在某个命名空间中定义的类吗?上面的语法感觉就像我在“重新打开”命名空间并扩展它的定义。如果Class 实际上没有在Namespace 中定义怎么办?这会在某个时候导致错误吗?

【问题讨论】:

让我不同意这里的所有答案,并说这只是该语言的设计错误。他们可以想得更好。 这倾向于讨论为什么这在 C++ 中是非法的(这是主观的),并且看起来很有争议。投票结束。 编译器应该如何知道A::B 中的A 是命名空间标识符而不是类名? @STingRaySC:讨论是主观的,因为没有明确的答案为什么 C++ 会这样做,所以我们在推测。 (这个问题是一个霰弹枪问题,有一些问题已经得到了客观的答案。)在这一点上,我对争论的痕迹变得敏感,而且你同意 Pavel 认为这是 C++ 的错误特征是合格的。如果Namespace 是一个类或命名空间,为什么它很重要,我不会有任何问题。只是不要暗示可能会引发一场针对语法的语言火焰战争。 【参考方案1】:

你得到了正确的答案,让我试着重新措辞:

class Namespace::Class;

为什么我必须这样做?

您必须这样做,因为术语 Namespace::Class 告诉编译器:

...好的,编译器。去寻找 命名空间命名空间,并在 引用名为 Class 的类。

但是编译器不知道你在说什么,因为它不知道任何名为Namespace 的命名空间。即使有一个名为Namespace 的命名空间,如:

namespace Namespace

;

class Namespace::Class;

它仍然行不通,因为您不能从该命名空间外部声明一个命名空间内的类。您必须在命名空间中。

因此,您实际上可以在命名空间中前向声明一个类。只需这样做:

namespace Namespace

    class Class;
;

【讨论】:

所有其他答案都让我感到困惑,但是这个“你不能在命名空间之外从命名空间声明一个类。你必须在命名空间中。”是非常有用的提示。【参考方案2】:

因为你不能。在 C++ 语言中,完全限定名称仅用于引用 现有(即先前声明的)实体。它们不能用于引入实体。

而您正在实际上“重新打开”命名空间以声明新实体。如果类 Class 稍后被定义为不同命名空间的成员 - 它是一个完全不同的类,与您在此处声明的类无关。

一旦你到了定义预声明类的地步,你就不需要再次“重新打开”命名空间了。您可以在全局命名空间(或包含Namespace 的任何命名空间)中将其定义为

class Namespace::Class 
  /* whatever */
;

由于您指的是已在命名空间Namespace 中声明的实体,因此您可以使用限定名称Namespace::Class

【讨论】:

@STingRaySC:前向声明嵌套类的唯一方法是将声明放在封闭类的定义中。而且确实没有办法在定义封闭类之前前向声明嵌套类。 @STingRaySC:嵌套类可以被前向声明——看我的回答。 @John Dibling:嵌套类是在另一个类中声明的类。在命名空间内立即声明的类不是嵌套类。您的答案中没有任何关于 sensted 类的内容。【参考方案3】:

我想这与你不能像这样一次性声明嵌套命名空间的原因相同:

namespace Company::Communications::Sockets 

你必须这样做:

namespace Company 
  namespace Communications 
    namespace Sockets 
    
  

【讨论】:

这实际上并不是解释为什么你不能这样做的答案。 这是一个为我节省了大量时间的答案 C++17 添加了这个。 这里你知道所有的都是命名空间。但是对于 Company::Communications::Socket 类,您不知道 Communications 是命名空间还是类(其中 socket 是嵌套类)。【参考方案4】:

不清楚前向声明变量的实际类型是什么。前向声明 class Namespace::Class; 可能意味着

namespace Namespace 
  class Class;

class Namespace 
public:
  class Class;
;

【讨论】:

不是impossible to forward-declare a class that's embedded into a class吗? 我认为这是最好的答案之一,因为它回答了为什么编译器本身无法轻易确定。【参考方案5】:

关于禁止它的理由有很多很好的答案。我只想提供无聊的标准条款,明确禁止它。这适用于 C++17 (n4659)。

有问题的段落是[class.name]/2:

仅由类键标识符组成的声明;要么是 在当前范围内重新声明名称或转发 将标识符声明为类名。它介绍了类 名称到当前范围内。

上面定义了前向声明(或类的红色声明)的构成。本质上,它必须是class identifier;struct identifier;union identifier; 之一,其中identifer 是[lex.name] 中的常用词法定义:

identifier:
  identifier-nondigit
  identifier identifier-nondigit
  identifier digit
identifier-nondigit:
  nondigit
  universal-character-name
nondigit: one of
  a b c d e f g h i j k l m
  n o p q r s t u v w x y z
  A B C D E F G H I J K L M
  N O P Q R S T U V W X Y Z _
digit: one of
  0 1 2 3 4 5 6 7 8 9

这是我们都熟悉的通用方案[a-zA-Z_][a-zA-Z0-9_]*的产生方式。如您所见,这使class foo::bar; 无法成为有效的前向声明,因为foo::bar 不是标识符。这是一个完全限定的名称,有些不同。

【讨论】:

以上是关于为啥我不能使用双冒号在命名空间中前向声明一个类?的主要内容,如果未能解决你的问题,请参考以下文章

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

为啥我们不能在一个类中声明一个命名空间?

为啥我不能只用前向声明 C++ 声明一个类的静态成员?

包含命名空间的类模板的转发声明会导致编译错误

双冒号是啥意思?

为啥不能对 std::vector 使用前向声明?