一,关于编译链接
编译指的的把编译单元生成目标文件的过程
链接是把目标文件链接到一起的过程
编译单元:可以认为是一个. c 或者. cpp 文件。每个编译单元经过预处理会得到一个临时的编译单元。预处理会间接包含其他文件还会展开宏调用。
每个编译单元编译成目标文件后会暴露自己内部的符号。
(比如有个 fun 函数,就会暴露出于 fun 函数对应的符号,其他的函数和变量也是一样的。但是也有不会暴露出去的,比如加了 static 修饰的函数或变量)
每个目标文件都有自己的符号导入表和符号导出表。
链接器根据自己所需要的符号去找其他的目标文件。
(假如 main 用到了别的文件定义发 fun 函数,在链接的过程中,链接器知道 mian 需要 fun 符号,然后去其他的目标文件总找。如果找到了就链接起来。找不到就报链接错误)
二、模板函数
模板函数的代码并不能直接编译成二进制代码,其中要有一个实例化的过程。模板被用到的时候才会进行实例化。
1. 假设有个 test.h 里面声明了模板函数。
test.cpp 实现了那个模板函数。
main 用到了那个模板函数。
编译器会编译 test.cpp 编译单元和 main.cpp 编译单元。
编译 test.cpp 时无法给出 A
main.cpp 需要一个这样的符号 A
在分离式编译的环境下,编译器编译某个 cpp 文件时并不知道另外的 cpp 的存在,也不会去查找(当遇到未决符号时他会寄希望于链接器)。
这种模式在没有模板的情况下运行良好,但是遇到模板时就不行了,因为模板仅在需要的时候才会实例化出来。
所以当编译器只看到模板的声明时,它不能实例化该模板,只能创建一个具有外部连接的符号,并期待链接器能够将符号的地址决议找出来。
然而实现该模板的 cpp 文件并没有用到该模板时,编译器就不会去实例化。
所以整个工程当中找不到模板实例的代码,链接器就找不到那个符号。就会报错了。
3. 实例:
test.h
#ifndef __CAR_H__
#define __CAR_H__
#include
using namespace std;
#define IN_CPP 1
template <class T>
class car
{
public:
car(T a);
void print();
public:
T data;
};
#if IN_CPP
#else
template <class T>
car<T>::car(T a)
{
data = a;
}
template <class T>
void car<T>::print()
{
cout << "data = " << data << endl;
}
#endif
#endif // __CAR_H__
test.cpp
#include "car.h"
#if IN_CPP
template <class T>
car<T>::car(T a)
{
data = a;
}
template <class T>
void car<T>::print()
{
cout << "data = " << data << endl;
}
#endif
void callTest()
{
car a(33);
a.print();
}
main.cpp
#include
#include "car.h"
using namespace std;
void fun()
{
cout << "fun() +++" << endl;
car a(99);
a.print();
}
int main()
{
fun();
return 0;
}
分析:
IN_CPP 如果是 0:就相当于声明实现都在头文件中。这样 main.cpp 是可以编译运行的。
IN_CPP 如果是 1:说明声明跟实现分开了。这种情况 main.cpp 链接时找不到 car 构造相关的函数,也找不到模板类 car 中 print 的函数。会报两个链接错。
但是如果在 test.cpp 写个函数(callTest())调用 car 的构造和 print,相当于实例化了那两个类模板函数。就会导出那两个函数的符号。假如只调用一个构造,那么 print 就没有实例化。main 也会链接失败
然后在 main.cpp 就可以调用到了。
来源: http://www.cnblogs.com/xcywt/p/8039574.html