最近在仿写stl,发现stl源码中将模板的声明与定义写在一起实在很不优雅。自己尝试用“传统”方法,及在.h文件里声明,在.cpp文件里定义,然后在main函数里包含.h头文件,这样会报链接错误。这是因为函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件,如果该头文件中只有声明,没有定义,那编译器无法实例化该模板,最终导致链接错误。

上面这句话有点抽象。要理解为什么会出错,首先要理解用传统方法写非模板函数时,编译器是怎么运作的。举个例子

//---------------test.h-------------------//
void f();//这里声明一个函数f
//---------------test.cpp--------------//
#include”test.h”
void f()
{
…//do something
} //这里实现出test.h中声明的f函数
//---------------main.cpp--------------//
#include”test.h”
int main()
{
f(); //调用f
}

编译时会生成两个obj文件,main.obj和test.obj,而在main.obj里并没有f函数的二进制代码,这些代码实际存在于test.obj中。在main.obj中对f的调用只会生成一行call指令,call指令的地址由链接器生成。

再看一个函数模板的例子

//-------------test.h----------------//
template<class T>
class A
{
public:
void f(); //这里只是个声明
};
//---------------test.cpp-------------//
#include”test.h”
template<class T>
void A<T>::f()
{
…//do something
}
//---------------main.cpp---------------//
#include”test.h”
int main()
{
A<int> a;
a. f();
}

我们知道模板有个具现化的过程,在未被使用的时候是不会生成二进制文件的。所以当链接器去找f函数的地址时,因为在这之前没有调用过f(),test.obj里自然就没有f函数的二进制代码,于是就会报错。

要使模板声明与定义分开也不是没有办法。

第一种办法是在main函数里包含cpp文件

//-------------test.h----------------//
template<class T>
class A
{
public:
void f(); //这里只是个声明
};
//---------------test.cpp-------------//
#include”test.h”
template<class T>
void A<T>::f()
{
…//do something
}
//---------------main.cpp---------------//
#include”test.cpp”
int main()
{
A<int> a;
a. f();
}

这样三个文件的内容通过include实际上包含在同一个文件里,自然就不会出错了。同理,还可以这样

//-------------test.h----------------//
template<class T>
class A
{
public:
void f(); //这里只是个声明
};
#include<test_impl.h>
//---------------test_impl.h-------------//
template<class T>
void A<T>::f()
{
…//do something
}
//---------------main.cpp---------------//
#include”test.h”
int main()
{
A<int> a;
a. f();
}

这两种方法实际上都是包含编译,没有本质的区别,不过感觉第二种方法看起来比较舒服。还有一种比较hack的方法

//-------------test.h----------------//
template<class T>
class A
{
public:
void f(); //这里只是个声明
};
//---------------test.cpp-------------//
#include”test.h”
template<class T>
void A<T>::f()
{
…//do something
}
template class A<int>;
//---------------main.cpp---------------//
#include”test.h”
int main()
{
A<int> a;
a. f();
}

这样由于在test.cpp里实例化了A<int>,所以链接器能够找到相关代码,就不会报错。但是这样main函数要用哪种类型的模板都得在test.cpp先实例化,很不方便。

以上都是包含编译的方法,其实C++0x有个export关键字,使模板能分离编译,但是基本没有编译器支持,在c++11中就废除掉了,所以这里就不提了。

c++函数模板声明与定义相分离的更多相关文章

  1. 你好,C++(24)好大一个箱子!5.1.1 函数的声明和定义

    第5章 用函数封装程序功能 在完成功能强大的工资程序V1.0之后,我们信心倍增,开始向C++世界的更深远处探索. 现在,我们可以用各种数据类型定义变量来表达问题中所涉及的各种数据:用操作符连接这些变量 ...

  2. C++类模板声明与定义为何不能分开

    我们用C++写类的时候,通常会将.cpp和.h文件分开写,即实现和声明分开写了:但在C++的类模板中,这种写法是错误的. 在<C++编程思想>的第16章的"16.3模板语法&qu ...

  3. C++模板编程:如何使非通用的模板函数实现声明和定义分离

    我们在编写C++类库时,为了隐藏实现,往往只能忍痛舍弃模版的强大特性.但如果我们只需要有限的几个类型的模版实现,并且不允许用户传入其他类型时,我们就可以将实例化的代码放在cpp文件中实现了.然而,当我 ...

  4. C语言,函数的声明与定义

    函数声明与定义 变量: 在讲变量前,先讲一下变量的声明和定义这两个概念. 声明一个变量,意味着向编译器描述变量的类型,但不为变量分配存储空间. 定义一个变量,意味着在声明变量的同时还要为变量分配存储空 ...

  5. 面试问题之C++语言:类模板声明与定义为何不能分开

    C++中每个对象所占用的空间大小,是在编译的时候就确定的,在模板类没有真正的被使用之前,编译器是无法知道,模板类中使用模板类型的对象的所占用的空间的大小的.只有模板被真正使用的时候,编译器才知道,模板 ...

  6. C++-函数模板特化如何避免重复定义

     我正在用一个基于模板的库源代码,该库包含一些针对特定类型的模板函数特化.类模板,函数模板和模板函数特化都在头文件中.我在我的.cpp文件中 #include 头文件并编译链接工程.但是为了在整个工程 ...

  7. [转]C++函数模板与模板函数

      1.函数模板的声明和模板函数的生成   1.1函数模板的声明 函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免重载函数的函数体重复设计.它的最大特点是把函数使用的数据类型作为参数. ...

  8. C++ 函数模板与类模板(使用 Qt 开发编译环境)

    注意:本文中代码均使用 Qt 开发编译环境,如有疑问和建议欢迎随时留言. 模板是 C++ 支持参数化程序设计的工具,通过它可以实现参数多态性.所谓参数多态性,就是将程序所处理的对象的类型参数化,使得一 ...

  9. 为什么 c++中函数模板和类模板的 声明与定义需要放到一起?

    将模板的声明与定义写在一起实在很不优雅.尝试用“传统”方法,及在.h文件里声明,在.cpp文件里定义, 然后在main函数里包含.h头文件,这样会报链接错误.why!!!!!!!!!!!!! 这是因为 ...

随机推荐

  1. sql - 查询所有表中包含指定值

    可以直接创建sql语句: CREATE TABLE qResults (tName nvarchar(370), cname nvarchar(3630),[count] int) declare @ ...

  2. 获取SqlServer当前链接数

    1.提供有关 Microsoft SQL Server 数据库引擎实例中的当前用户.会话和进程的信息,显示所有session sp_who 2.针对 SQL Server 上的每个经过身份验证的会话返 ...

  3. 关于Androdi中SQLITE 3采用GBK编码存储,数据库中文乱码问题。

    1.最近开发一个项目,用SQLite Expert Personal打开数据库如下图,title会产生乱码,问题. 2.由于SQL lite默认是存储UTF-8格式,后来更改数据库编码类型为ANSI, ...

  4. 转:Android软件开发之PreferenceActivity中的组件

    本文转于 “雨松MOMO的程序世界” 博客,请务必保留此出处http://xys289187120.blog.51cto.com/3361352/656784 1.PreferenceActivity ...

  5. 将P2P虚拟货币(比特币、莱特币....)的算力用于公共的分布式计算的猜想

    比特币最近几年非常火爆.发明者中本聪设计了一个特定的算法用于生成(发行)比特币,让各位玩家(矿工)用自己的CPU.显卡,或者更加专业的矿机,通过无聊的并行计算算出比特币的特定密码(挖矿).为了保证全网 ...

  6. Bresenham画直线,任意斜率

    function DrawLineBresenham(x1,y1,x2,y2) %sort by x,sure x1<x2. if x1>x2 tmp=x1; x1=x2; x2=tmp; ...

  7. 《Velocity java开发指南》中文版(上)转载

    文章引自:http://sakyone.iteye.com/blog/524289 1.开始入门 Velocity是一基于java语言的模板引擎,使用这个简单.功能强大的开发工具,可以很容易的将数据对 ...

  8. 一次 php nusoap 调试过程

    今天跟同事调用一个数据api ,用soap方式调用.本以为很简单的事情,却弄到了晚上. 因为有过调试经验,直接按照以往的过程直接部署,结果是错误. 1. 以为是调用方式错了,问了一下对接的同事,没问题 ...

  9. 编译u-boot命令和u-boot常用命令

    一.编译u-boot命令 1.配置开发板 #make TQ2440_config 2.编译 #make all 3.交叉编译器是crosstools_3.4.5_softfloat” 使用4.3.3版 ...

  10. 典当行以及海尔java小节

    1.视图问题,发现jar包都出现在根目录下面了,非常不方便.结果如下表: 原始视图是JavaEE,切换到Java视图即可: 2.Tomcat编译的时候什么都没有加载,看到的是一堆红字,那是因为tomc ...