0%

C++从源文件到可执行文件:编译

编译的过程为编译原理所涉及的内容,不做展开。

主要关注:

  • 模板在阶段7,8进行的两阶段查找
  • 模板为何要放到头文件中:包含编译模式(inclusion model)
  • 最常见的编译器实现:贪婪具现化(greedy instantiation)

模板

两阶段查找

模板具现化是通过正确地替换模板中的模板参数,来获得类型、函数或变量的过程。C++标准规定模板的具现化过程为两阶段查找(two-phase lookup):

  • 第一阶段:解析模板

    • 在 非待决名 上使用 有限定的名字查找 或 无限定的名字查找。
    • 在 无限定的待决名 上使用无限定的名字查找,确认其是否是一个模板。然而我们在第一阶段不进行ADL。ADL被推迟到第二阶段。我们认为此次查找的结果暂时是不完整的。
    • 此时无法处理有限定的待决名。
  • 第二阶段:模板实例化

    • 在实例化点(POI: point of instantiation)对模板进行实例化。
    • 对 有限定的待决名 进行 有限定的名字查找
    • 对 无限定的待决名 进行 ADL 将结果与第一阶段进行的 无限定的名字查找 的结果合并。

例子一:为什么需要两阶段查找?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
template<typename T>
class Trap
{
public:
enum { x }; // #1
};

template<typename T>
class Victim
{
public:
int y;
void poof()
{
Trap<T>::x * y; // #2
}
};

template<>
class Trap<void>
{
public:
using x = int; // #3
};

void boom(Victim<void>& bomb)
{
bomb.poof();
}

在主模板的#1处,x的类型为一个enum。在全特化模板的#3处,x是一个int的alias。因此在#2处的有限定的待决名Trap<T>::XTrap<T>未实例化时无法被决议。如果T是一个void,则该语句生命了一个int*,如果T是其他类型,则该语句为一个乘法运算。在#2的只能确定该标识符是一个有限定的待决名,其他都不可知。只有等到Trap<T>实例化以后,我们才能对其进行决议。因此两阶段查找的必要的。第二阶段就发生在实例化以后。


例子二:

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename T>
void f1(T x)
{
g1(x); // #1
}

void g1(int){}

int main()
{
f1(7); // 错误:g1 not found
}
// #2 POI for f1<int>(int)

在第一阶段#1位置的g1(x),因为依赖于x的类型,因此是一个无限定的待决名。此时我们对其进行 无限定的名字查找,由于g1(int)在其下方,因此 无限定的名字查找 查找结果为空。第二阶段在#2处,POI后进行ADL。由于参数类型为int,因此生成的类和命名空间的集合都为空,因此对g1的查找失败。


实例化点(point of instantiation)

当代码中涉及到某一模板的特化时,该模板必须被具现化以产生该特化。因此编译器必须在该涉指附近安插代码以使得该特话被具现化。安插代码的位置就被称作实例化点

标准规定:
The POI for a reference to a function template specialization is immediately after the nearest namespace scope declaration or definition that contains that reference.

The POI for a reference to a class template specialization is immediately before the nearest namespace scope declaration or definition that contains that reference.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MyInt
{
public:
MyInt(int i);
};

MyInt operator - (const MyInt&);
bool operator > (const MyInt&, const MyInt&);

using Int = MyInt;

template<typename T>
void f(T i)
{
if(i > 0)
{
g(-i); // call g
}
}

// #1
void g(Int)
{
// #2
f<Int>(42); // point of call
// #3
}
// #4

为了实例化//point of call位置的函数模板f<Int>(int),我们需要在其附近插入::f<int>(int);作为实例化点。位置#2#3在函数内部,C++中不可以在函数内部直接插入另一个函数的声明。位置#1#4的区别在于在#1处,void g(Int)还不可见。如果在#1处插入POI,则无法解析// call g处对g调用。因此实例化点被安排在#4#4作为实例化点也是符合标准的,因为包含//point of call的最近的命名空间作用域为函数void g(int)的命名空间作用域,其immediately after的位置刚好是#4


Inclusion Compilation Model

贪婪实例化(greedy instantiation)