前言
本文首发于我的公众号:码农手札,主要介绍linux下c++开发的知识包括网络编程的知识同时也会介绍一些有趣的算法题,欢迎大家关注,利用碎片时间学习一些编程知识,冰冻三尺非一日之寒,让我们一起加油!
说实话,我个人是不怎么使用模板的,使用场景很少,因此在上一篇博客里面难得用了一次模板,反而出了点小问题,我遇到的问题就是把声明和实现分开在h文件和cpp文件,因此在编译的时候怎么都无法通过,简直懵逼了,不过好在上网搜了一下突然想起来好像之前看到过模板的声明和实现必须都写在一个头文件中,这里我们来简单研究下为什么cpp里面,模板和声明是必须放在一起的。
测试代码
让我们先写一份非模版实现的代码,然后再写一份使用模板实现的代码,使用一些辅助工具来看看有什么不同,下面先展示一份非模板实现的代码
///test.h
#pragma once
class Test {
public:
Test(const int &t) : ele_(t) {}
int get_ele();
private:
int ele_;
};
///test.cpp
#include "test.h"
int Test::get_ele() { return ele_; }
///test_main.cpp
#include "tetest_
#include
int main(int argc, char const *argv[]) {
Test test(5);
std::cout << test.get_ele() << std::endl;
return 0;
}
下面是使用模板实现的代码
///test_template.h
#pragma once
template class TestTemplate {
public:
TestTemplate(const T &t) : ele_(t) {}
T get_ele();
private:
T ele_;
};
///test_template.cpp
#include "test_template.h"
template T TestTemplate::get_ele() { return ele_; }
///main.cpp
#include "test_template.h"
#include
int main(int argc, char const *argv[]) {
TestTemplate test_template(5);
std::cout << test_template.get_ele() << std::endl;
return 0;
}
非模板代码编译结果
很显然,按照c++的规则,前一种实现方式是完全没有问题的,但是后一种将模板声明和实现分开的方式编译是无法通过的,那么让我们试试看看到底是什么情况
g++ -c test.cpp
获得test.o文件,然后使用nm来查看里面有什么函数
nm test.o
在我电脑上,输出为0000000000000000 T __ZN4Test7get_eleE,这个时候,我们能够猜到这个就是get_ele这个函数的定义,其实这个新的名字是c++的一种给函数定义唯一标示的一种方式,这个是根据命名空间以及函数长度包括参数类型生成的,有一定的规则,不过这个其实没必要太关注(这里我多提一句,因为生成新名字的时候并不包括返回值的类型,这就是c++要求重载函数必须要求函数参数不同的原因)。不过当我们在遇到程序bug的时候,使用gdb也会看到这个东西,这个时候我们需要查看这个函数,这里我介绍一个辅助工具查看函数实际的名字
c++ __ZN4Test7get_eleE
这个命令会告诉我们那个名字对应的真实名字,我这里对应的是Test::get_ele(),不多提,也就是说Test::get_ele()的实现是在test.o文件里面的,当执行到编译的第四个阶段的时候也就是链接,test_main.o会告诉链接器,它需要Test::get_ele()的定义,这个时候链接器发现,哎,test.o文件里面,那正好,然后顺利生成可执行文件,到这里都是顺理成章,那么,看起来我们可以猜测将模板类的声明和实现分开的话,是否链接器无法找到它想要的东西呢,让我们继续下一步。
模板代码编译尝试
这里,毋庸置疑,直接编译肯定是会失败的,那么让我们来看看,到底是哪里出了问题,根据编译报错,Undefined symbols for architecture x86_64: "TestTemplate
g++ -c test_template.cpp
获得test_template.o文件,然后我们用老办法,使用nm来看看test_template.o里面有没有我们想要的东西
nm test_template.o
终端什么都没输出,不过我们对于这个结果也不意外,因为要是有定义才奇怪了,这里我来解释一下,为什么没有定义呢,因为c++标准规定,如果当一个模板没有被用到的时候,它就不该被实现出来,这里由于test_template.cpp里面并没有任何地方用到了TestTemplate
换种方式
我们将test_template.cpp改成下面这样
#include "test_template.h"
int test() {
TestTemplate t(5);
return t.get_ele();
}
template T TestTemplate::get_ele() { return ele_; }
然后我们再编译,这个时候我们就会惊奇的发现,编译完全正常,这里我们仅仅添加了一个无法被外界调用的test函数(因为我们并没有在头文件种声明test函数),但是正是由于test函数中用到了TestTemplate
总结
到这里,文章就写的差不多了,这篇文章其实没啥很难的地方,本质上其实就是说c++要求模版声明和实现放在一个头文件,不过这里介绍了几个小命令,让我们熟悉一下c++的编译链接过程,以及一些规则。