一:传统的编译模型

使用C/C++进行编程时,一般会使用头文件以使定义和声明分离,并使得程序以模块方式组织。将函数声明、类的定义放在头文件中,而将函数实现以及类成员函数的定义放在独立的文件中。

但是对于模板来说,这种方式是行不通的,具体的例子如下:

首先是包含模板声明的头文件temp.h:

//temp.h

#ifndef TEMP_H
#define TEMP_H template<typename T>
int compare(const T &a, const T &b); template<typename T>
class testtemp
{
public:
testtemp(const T &a):m_value(a){}
void display(); private:
T m_value;
}; #endif

    该头文件中包含了一个函数模板的声明,以及一个类模板的定义。

下面是包含模板定义的源码文件temp.cpp:

//temp.cpp

#include <iostream>
#include "temp.h" template<typename T>
int compare(const T &a, const T &b)
{
if (a < b) return -1;
if (b < a) return 1;
return 0;
} template <typename T>
void testtemp<T>::display()
{
std::cout << m_value << std::endl;
}

下面是主函数文件main.cpp:

//main.cpp

#include <iostream>
#include "temp.h" int main()
{
int a = 1, b = 3;
int res; testtemp<int> tt(4);
tt.display(); res = compare(a, b);
std::cout << "res is " << res << std::endl;
}

  

对上面的文件编译生成可执行文件时,会报错:

# g++ -o main main.cpp temp.cpp

/tmp/ccNwfO8x.o: In function `main':

main.cpp:(.text+0x47): undefined reference to `testtemp<int>::display()'

main.cpp:(.text+0x5a): undefined reference to `int compare<int>(int const&, int const&)'

collect2: error: ld returned 1 exit status

  

报错的原因如下:

C++中每一个对象所占用的空间大小,都是在编译的时候就确定的。在编译阶段,源码文件main.cpp将包含模板声明的头文件temp.h包含进来之后,编译器就需要为main.cpp中涉及到的每个对象生成合适的内存布局,为每个函数生成相应的指令。

当源码文件main.cpp中涉及到模板类成员函数或者模板函数的调用时,因为模板函数的定义在另一个源码文件temp.cpp中,编译器目前仅仅知道它们的声明。所以,在main.cpp中调用到的的testtemp<int>::display函数,以及int compare<int>(int const&, int const&)函数,编译器认为这些函数的实现是在其他源码文件中的,编译器不会报错,因为连接器会最终将所有的二进制文件进行连接,从而完成符号查找,形成一个可执行文件。

尽管编译器也编译了包含模板定义的源码文件temp.cpp,但是该文件仅仅是模板的定义,而并没有真正的实例化出具体的函数来。因此在链接阶段,编译器进行符号查找时,发现源码文件中的符号,在所有二进制文件中都找不到相关的定义,因此就报错了。

二:模板的编译模型

当编译器看到模板定义的时候,它不立即产生代码。只有在看到用到模板时,如调用了函数模板或调用了类模板的对象的时候,编译器才产生特定类型的模板实例。

一般而言,当调用函数的时候,编译器只需要看到函数的声明。类似地,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的。因此,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中。

模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码。当调用函数模板或类模板的成员函数的时候,编译器需要函数定义,需要那些通常放在源文件中的代码。

标准 C++ 为编译模板代码定义了两种模型。分别是包含编译模型和分别编译模型。

所谓包含编译模型,说白了,就是将函数模板的定义放在头文件中。因此,对于上面的例子,就是将temp.cpp的内容都放到temp.h中。

包含编译模型有个问题,如果两个或多个单独编译的源文件使用同一模板,这些编译器将为每个文件中的模板产生一个实例。因此给定模板会产生多个相同的实例,在链接的时候,编译器会选择一个实例化而丢弃其他的。

在分别编译模型中,编译器会为我们跟踪相关的模板定义。但是,我们必须让编译器知道要记住给定的模板定义,因此需要使用 export 关键字。但是,实际上很多编译器都不支持这个关键字,而且C++11 将这个关键字设置为 unsued 和 reserved 了。

所以,结论就是,把模板的定义和实现都放到头文件中。

参考:

http://gaunthan.leanote.com/post/C-%E6%A8%A1%E6%9D%BF%E7%9A%84%E7%BC%96%E8%AF%91%E6%A8%A1%E5%9E%8B

https://www.zhihu.com/question/20630104

C++模板编译模型的更多相关文章

  1. C++ Primer 学习笔记_79_模板与泛型编程 --模板编译模型

    模板与泛型编程 --模板编译模型 引言: 当编译器看到模板定义的时候,它不马上产生代码.仅仅有在用到模板时,假设调用了函数模板或定义了模板的对象的时候,编译器才产生特定类型的模板实例. 一般而言,当调 ...

  2. vue模板编译

    Vue 的模板编译是在 $mount 的过程中进行的,在 $mount 的时候执行了 compile 方法来将 template 里的内容转换成真正的 HTML 代码. complie 最终生成 re ...

  3. 【转】【Html】Vuejs2.0学习之二(Render函数,createElement,vm.$slots,函数化组件,模板编译,JSX)

    1.Render函数 所以直接来到Render,本来也想跳过,发现后面的路由貌似跟它还有点关联.先来看看Render 1.1 官网一开始就看的挺懵的,不知道讲的是啥,动手试了一下,一开头讲的是Rend ...

  4. smarty 模板编译和变量调节器 模板引入

    <?php require './smarty/Smarty.class.php'; $sm = new Smarty; //$sm->force_compile = true; $sm- ...

  5. es6实现简单模板编译

    现在有各种框架,其中一个主要模块就是关于template.最火的vue.react等框架,在这一块上也是是下足了功夫.我也想写一个自己的模板编译工具,所以就做了个简单的实现,主要是使用es6的反引号编 ...

  6. Vuejs2.0学习之二(Render函数,createElement,vm.$slots,函数化组件,模板编译,JSX)

    时隔一周多,因为一些别的事情绊住了,下面接着写.中间这段时间也有看官方文档,发现正如他所说90%的基础内容都一样,所以这里直接跳到我比较关注的东东上,要是想看看哪些不一样,可以参考这个http://v ...

  7. [Vue源码]一起来学Vue模板编译原理(一)-Template生成AST

    本文我们一起通过学习Vue模板编译原理(一)-Template生成AST来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学Vu ...

  8. [Vue源码]一起来学Vue模板编译原理(二)-AST生成Render字符串

    本文我们一起通过学习Vue模板编译原理(二)-AST生成Render字符串来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学V ...

  9. CASthesis 模板编译的问题

    使用CASthesis模板(https://github.com/xiaoyao9933/UCASthesis,介绍里说这个版本是在ThuThesis的基础上修改的),自带的说明如下. 使用须知 == ...

随机推荐

  1. 开发者说 | 分布式事务中间件 Seata 的设计原理

    导读 微服务架构体系下,我们可以按照业务模块分层设计,单独部署,减轻了服务部署压力,也解耦了业务的耦合,避免了应用逐渐变成一个庞然怪物,从而可以轻松扩展,在某些服务出现故障时也不会影响其它服务的正常运 ...

  2. JS 重载页面,本地刷新,返回上一页

    JS 重载页面,本地刷新,返回上一页 : <a href="javascript:history.go(-1)">返回上一页</a> <a href= ...

  3. python 实现发送短信验证码

    [说明]短信接口使用的是“聚合数据”上面的接口. 那么在使用接口前,需要在聚合数据上面注册,进行申请接口.当然在正式使用之前,我们可以使用申请免得的进行测试. 一.申请成功后,需做的准备工作如下: 1 ...

  4. Spring这棵大树

    目前项目中用的框架大多数都是Spring,一直想找时间对这个框架做一个全面的了解. 今天先以导图的形式画出来Spring都包含的主要模块,即使有些模块目前用不上,但说不定在将来的应用场景时想到Spri ...

  5. Oil Deposits HDU - 1241 (dfs)

    Oil Deposits HDU - 1241 The GeoSurvComp geologic survey company is responsible for detecting undergr ...

  6. MySQL--修改Mac中的默认编码

    1.在终端中进入到etc目录下 2.打开etc目录下的my.cnf文件(如果这样修改不了的就要提高用户权限, 可以尝试使用sudo来打开文件) 3.将一下内容添加到my.cnf文件中 [client] ...

  7. day38 11-Spring的Bean的属性的注入:对象属性

    package cn.itcast.spring3.demo5; public class Person { private String name;//人的名字 private Car2 car2; ...

  8. Leetcode657.Robot Return to Origin机器人能否返回原点

    在二维平面上,有一个机器人从原点 (0, 0) 开始.给出它的移动顺序,判断这个机器人在完成移动后是否在 (0, 0) 处结束. 移动顺序由字符串表示.字符 move[i] 表示其第 i 次移动.机器 ...

  9. thinkcmf 导航高亮制作方法(适用于多级导航)(通用)

    平时用thinkcmf网站开发经常需要导航点击之后高亮,就写了一些实现方法分享一下. 思路很简单,先获取当前页面的顶级栏目的地址,然后与导航中的地址比较,相同的就加上一个class,把下面函数理解了不 ...

  10. pycharm使用gitlab输错密码解决办法

    在pycharm中使用http方式连接gitlab,在测试连接的时候提示输入用户名,密码.密码输错后,以后的每次test都是使用错误的密码,在删除pycharm后也是一样,解决方法是在控制面板\用户帐 ...