看面经遇到一个C++模板的问题,顺手做了下实验看看结果,觉得比较有意思就记录一下
我们一般用模板会把声明和定义放在一起(放在同一个头文件内),那么如果我们在一个头文件内声明我们要使用的模板函数,并在另一个cpp文件内实现会怎么样?
// funcT.h
#pragma once
#include <iostream>
template<typename T>
void func2(T const& a, T const& b);
// funcT.cpp
#include "funcT.h"
#include <iostream>
template<typename T>
void func2(T const & a, T const & b)
{
T c = a + b;
std::cout << "func4" << std::endl;
return ;
}
g++ -c funcT.cpp -o funcT
编译一下,没问题。
在另一个文件内使用函数
// main.cpp
#include <iostream>
#include "funcT.h"
int main()
{
int x = 3;
int y = 2;
func2(x, y);
return 0;
}
g++ -c main.cpp -o main
编译一下也没问题。
然后我们将两个目标文件链接为一个可执行文件,很遗憾报错了,具体错误是对 void func2<int>(int const&, int const&)
未定义的引用
这是为什么呢,我们明明已经给了一个具体的定义,为什么链接器找不到呢。
我们在 func.cpp
中加一段代码
// funcT.cpp
#include "funcT.h"
#include <iostream>
template<typename T>
void func2(T const & a, T const & b)
{
T c = a + b;
std::cout << "func4" << std::endl;
return ;
}
void caller() {
func2(3, 4);
}
再编译->汇编->链接,不报错了,且能正常输出结果。
本质原因是模板是需要在编译的时候展开的,本身就不能算是“声明/定义”,或者说只是模板的声明和定义,而没有具体函数的定义,我们编译 func2.cpp
,对于第一个版本,里面没有调用 func2(T const& a, T const& b)
的代码,所以编译器没有展开模板的任何实现。所以,我们在 main.cpp
中调用的时候,展开的只是一个声明,定义是缺失的,所以链接的时候会报错。
而第二个版本,我们在 func2.cpp
中加入了一个 caller
函数,里面实际调用了func2
函数,所以在编译的时期生成了具体的函数定义,那么在链接的时候就能找到。
通过这个实验,更明确了模板展开是编译期行为。
另一个有意思的实验