一个类的编译是如何工作的?

5

关于编译/链接过程的参考资料有很多,但我对一个更具体的问题感兴趣:类的编译。

这个问题出现是因为通常需要先了解一些东西才能使用。例如:如果之前没有声明函数,就不能调用它。

在类中,情况并不相同。可以在成员出现之前使用它们。编译器会做什么?标准是否对先前阶段的编译有相关规定?

为了更具体,下面的例子展示了如何使用定义在其后的成员。

#include <iostream>

class EvenOdd {
public:
   EvenOdd(): value(0) {}

   void assignEven(unsigned v) {
      if (v>0 && v%2==1) {
         std::cout << "Wrong call... is odd" << std::endl;
         assignOdd(v);
      }
      else {
         std::cout << "Right..." << v << " is Even" << std::endl;
         value= v;
      }
   }
   void assignOdd(unsigned v) {
      if (v>0 && v%2==0) {
         std::cout << "Wrong call... is even" << std::endl;
         assignEven(v);
      }
      else {
         std::cout << "Right..." << v << " is Odd" << std::endl;
         value= v;
      }
   }
private:
   unsigned value;
};

int main()
{
  EvenOdd a;
  std::cout << "Do it right..." << std::endl;
  a.assignEven(2);
  std::cout << "doing it wrong..." << std::endl;
  a.assignEven(3);
}

我们还可以添加关于内联函数的更多问题,因为它们可能在调用点之后定义,并且编译器可以解决而没有问题。我猜答案是相关的。
更新:我知道编译/链接有几个步骤。另一方面,如果编译器接受调用下面定义的函数,那么是因为编译器在某种意义上已经分析了代码。问题是在之前完成了哪种类型的先前阶段?此外......在标准的哪个部分中,我们找到与使用下面定义的成员相关的内容?
了解编译器的工作方式非常有趣,因为它必须了解下面的函数(至少是头文件)的细节,这似乎实际上对于编译很重要。甚至数据成员也必须编译,因为您必须将其类型与上面函数的上下文相关联。
它的工作方式就像重新排序代码,但与上面的示例不一致,因为两个函数成员相互调用。就像重新排序数据成员和函数头一样,编译器可能会考虑的代码。

编译有多个步骤。 - axiac
1
@axiac 是的,但 OP 似乎已经知道了这一点。我猜他们真正想知道为什么需要对函数进行先前声明,但不需要对类成员进行声明。看起来确实不一致。 - luk32
谢谢@luk32,那就是问题! - EFenix
1
C++确实有一个奇怪的编译模型。它从C继承了单遍行为。除了其C与类的遗产,那时64 KB的内存成本高昂,磁盘驱动器又小又慢,这是相当必要的。但是,它必须使用两遍模型来处理内联函数。由于它仅适用于单个类,因此内存使用隐含受限。模板是更大的问题,当编译器不得不吞咽像Boost这样的库时,编译器往往会明显减慢。 - Hans Passant
2个回答

7
标准规定:
在类说明符的结束大括号 } 处,一个类被视为完全定义的对象类型(3.9),也就是完整类型。在类成员说明中,该类在函数体、默认参数、异常说明以及默认成员初始化器(包括嵌套类中的这些内容)中被视为完整类型。否则,在其自身类成员说明中,它被视为不完整类型。
特别是意味着成员函数体可以引用在其下声明的类成员。标准并不关心实现如何实现这一点。一种可能的方法是将成员函数体和其他上述元素的语义分析推迟到看到类的结束大括号时再进行。

0
类中的函数可以访问和修改(除非该函数是常量)数据成员,因为数据成员已经在类中声明过了。这就是为什么函数不需要在类中进行声明的原因。

很遗憾,晚了5分钟...(y) - hsqureshi

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