【问题标题】:Why doesn't C++ need forward declarations for class members?为什么 C++ 不需要类成员的前向声明?
【发布时间】:2011-09-09 04:11:58
【问题描述】:

我的印象是 C++ 中的所有内容都必须在使用前声明。

事实上,我记得读过这就是为什么在没有decltype 之类的情况下在返回类型中使用auto 是无效的C++0x 的原因:编译器必须知道评估函数体之前声明类型。

当我注意到(经过很长时间)以下代码实际上完全合法时,想象一下我的惊讶:

[编辑:更改示例。]

class Foo
{
    Foo(int x = y);
    static const int y = 5;
};

所以现在我不明白:

当编译器在其他地方需要前向声明时,为什么编译器不需要在类中进行前向声明?

【问题讨论】:

    标签: c++ forward-declaration


    【解决方案1】:

    标准说(第 3.3.7 节):

    类中声明的名称的潜在范围不仅包括名称声明点之后的声明区域,还包括所有函数体、非静态数据成员的大括号或等号初始化器和默认值该类中的参数(包括嵌套类中的此类内容)。

    这可能是通过将内联成员函数的处理体延迟到解析整个类定义之后来实现的。

    【讨论】:

    • 了解什么是“潜在范围”以及它在此处的关系会很有帮助。
    • @Mehrdad:标识符的(潜在)范围是该名称可用的源代码部分。根据 3.3 的介绍性文字,潜在作用域作用域不同,因为同名的局部变量可以隐藏类成员。
    • 哦,有趣。我想这回答了“为什么我的代码不会产生错误?”的问题。但不是“他们为什么以这种方式制作语言?”的根本问题,即为什么对前向声明的要求在类内部和类外部不同?
    • @Mehrdad:在类定义中编写成员函数体的能力相当重要。我想这也不是绝对必要的,但它确实很方便。
    • 我很困惑......为什么不能像全局函数一样在成员函数的情况下进行前向声明?
    【解决方案2】:

    类体内的函数定义被视为在定义类之后实际定义的。所以你的代码相当于:

    class Foo
    {
        Foo();
        int x, *p;
    };
    inline Foo::Foo() { p = &x; }
    

    【讨论】:

    • 应该有一个 ;如果我没记错的话,在类中的第一个 Foo() 声明之后
    • @K-ballo:这对我的例子来说是有道理的,但不是一般的。那么,如果 y 未声明,为什么会编译呢? class Foo { Foo(int x = y); static const int y = 5; };
    • 那很好,我猜想内联成员函数中的默认参数有一个特殊的规则?我也想听听答案!
    • 不,不等价。修改后Foo::Foo 不再是inline,会产生多个定义错误。
    【解决方案3】:

    其实我觉得你需要把问题倒过来才能理解。

    为什么 C++ 需要前向声明?

    由于 C++ 的工作方式(包括文件,而不是模块),否则它需要等待整个翻译单元才能确定地评估功能是什么。这里有几个缺点:

    • 编译时间将再次受到打击
    • 几乎不可能为标头中的代码提供任何保证,因为任何后续函数的引入都可能使其全部失效

    为什么类不同?

    根据定义,一个类是包含的。这是一个小单位(或应该是......)。因此:

    • 编译时间问题不大,可以等到课程结束再开始分析
    • 不存在依赖地狱的风险,因为所有依赖都被明确识别和隔离

    因此,我们可以避开这个烦人的类前向声明​​规则。

    【讨论】:

    • 这是一个很好的观点,但是为什么它需要前向声明来处理返回类型推断之类的事情呢?
    • @Mehrdad:在 C++ 中,函数签名位于主体之前。其他语言从主体推断参数/返回类型(有关使用的算法示例,请参见 Hindley-Milner),但 C++ 没有。它无疑简化了编译器的实现。
    【解决方案4】:

    只是猜测:编译器保存函数体,直到类声明完成才真正处理它。

    【讨论】:

    • 不错的答案,但请参阅我修改后的示例。
    • @Mehrdad,在实际调用函数之前不会计算默认参数。
    • @Mark:虽然在调用之前不会评估默认参数,但这并不意味着在调用站点执行名称解析。例如,在类主体和调用站点之间引入的函数重载(在命名空间范围内)将不予考虑。
    【解决方案5】:

    与命名空间不同,类的范围不能重新打开。它是绑定的。

    如果一切都需要提前声明,想象一下在头文件中实现一个类。我认为既然它是绑定的,那么按原样编写语言更合乎逻辑,而不是要求用户在类中向前编写(或要求将定义与声明分开)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-08
      • 2012-06-22
      • 2019-11-27
      • 1970-01-01
      相关资源
      最近更新 更多