为什么声明/定义顺序在C ++中仍然很重要?

时间:2017-11-19 11:21:38

标签: c++ c++11

现在很多次,我在C ++中遇到了声明和定义顺序的问题:

struct A {
    void Test() { B(); }
};

void B() {
    A a;
}

当然,这可以通过预先B()来解决。 通常这足以解决任何这些问题。但是当使用基于模块的仅头文件库或类似复杂的包含系统时,这种声明/定义概念可能非常痛苦。我在下面提供了一个简单的例子。

如今,大多数现代语言编译器对源文件进行两次传递,以在第一次传递中构建声明,并在第二次传递中处理定义。将此方案引入C ++也不应该破坏任何旧代码。因此,

  • 为什么还没有将此类或类似的方法引入到c ++中?
  • 现行标准中是否有任何相关条款禁止这种做法?

实施例

这是基于模块的头库的示例,由于缺少预先声明,因此包含阻塞。为了解决这个问题,图书馆的用户必须预先确定"缺失"类,这是不可行的。 当然,这个问题可以通过使用在定义之前命令所有声明的公共include头来解决,但是使用两遍这个代码也可以工作,不需要修改。

oom.h

#pragma once
#include "string.h"

struct OOM {
    String message;
};

string.h

#pragma once
#include "array.h"

struct String {
    Array data;
};

array.h

#pragma once

struct Array {
    void Alloc();
};

#include "oom.h"

void Array::Alloc() { throw OOM(); }

str_usage.cpp

#include "string.h"
int main() {
    String str;
}

3 个答案:

答案 0 :(得分:2)

void f(int);
void g() { f(3.14); }
void f(double); 

g目前正在调用f(int),因为它是f唯一可见的。它在你的世界中叫什么?

  • 如果它调用了f(double),那么您只需打破大量现有代码。
  • 如果你提出一些规则让它仍然调用f(int),那就意味着我写了

    void g2() { f2(3.14); }
    void f2(double);
    

    然后为参数引入更糟糕的匹配 - 比如void f2(int);之前的g2g2会突然开始调用错误的东西。这是可维护性的噩梦。

答案 1 :(得分:1)

一个更简单的解决方案是将类定义与函数定义分开:

struct A {
    void Test();
};

struct B {
    A a;
};

inline void A::Test() {
    B();
}

答案 2 :(得分:0)

C ++语法中存在歧义,只有在知道标识符引用的内容时才能解决。

例如:

a * b;
如果a是变量,则

可以是乘法,如果a是类型,则可以是指针声明。这些中的每一个都会导致不同的解析树,因此解析器必须知道a是什么。

这意味着解析和名称解析不能在单独的传递中执行,但必须在一次传递中完成,这导致需要预先声明名称。