一、介绍
C++中,普通函数的声明和实现通常分别写在.h和.cpp中,但模板的声明和实现必须写在同一份文件中,否则编译会报错。
二、原理
C++的编译可以分为4个过程:预编译、编译、汇编和链接。对于普通函数,声明写在.h中,在预编译过程中,.h文件中的内容会被插入带对应的#include指令所在位置。在编译过程中,每个.cpp文件均会单独编译成二进制文件,如果此时调用函数的.cpp文件中找不到函数定义,那么就会将其标记为未解决符号(unresolved symbol)。在链接过程中,再整合解决符号引用问题。
// add.h
// 在预编译阶段,分别插入到add.cpp和main.cpp中
#ifndef _ADD_H_
#define _ADD_H_
int add(int a, int b);
#endif
// add.cpp
// 在编译阶段,会生成二进制文件add.o
#include "add.h"
int add(int a, int b) {
return a + b;
}
// main.cpp
// 在编译阶段,会生成二进制文件main.o,并且在add函数的符号是未解决符号
// 在链接阶段,通过符号表解决add函数的符号问题
#include "add.h"
#include <iostream>
int main() {
std::cout << add(1, 2) << std::endl;
return 0;
}
而对于模板来说,实现不是实例化,模板的具体实例化一般并不会在代码中书写,因此模板的实现并不会生成二进制文件(因为没有实例化)。模板的实例化是在编译过程中,编译器预见模板使用时才会进行,如果此时.h文件中只有声明没有实现,那么编译器就无法正常实例化,则出现编译错误。
// add.h
// 声明和实现在同一文件中,也可以是.cpp或.hpp
#ifndef _ADD_H_
#define _ADD_H_
template <typename T>
class Add {
public:
T add(T a, T b);
};
template <typename T>
T Add<T>::add(T a, T b) {
return a + b;
}
#endif
// main.cpp
// 在编译阶段,编译器会在调用出实例化,即参考add.h生成Add<int>的具体代码
#include "add.h"
#include <iostream>
int main() {
Add<int> s;
std::cout << s.add(1, 2) << std::endl;
return 0;
}