最近在仿写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. Java23种设计模式之单例模式

    一.单例模式简介 单例模式是Java设计模式中常见的一种模式.主要分为懒汉式单例.饿汉式单例.登记式单例: 单例模式的特点:  1.单例类只能有一个实例:  2.单例类必须自己创建自己的唯一的实例: ...

  2. JS DOM 来控制HTML元素

    JS DOM 来控制HTML元素 (ps:这个有很多方法,挑一些详解,嘻嘻) 1.getElementsByName():获取name. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ...

  3. 解决打不开jar包

    Java应用程序jar文件可以由 JVM(Java虚拟机)直接执行,只要操作系统安装了JVM便可以运行作为Java应用程序的jar文件,其跨平台特性使得很多工具软件都用jar方式来部署分发,比如用于H ...

  4. jQuery入门[3]-事件

    jQuery对事件的支持主要包括: bind()--为事件绑定处理程序,如: $("p").bind("mouseenter mouseleave", func ...

  5. 配置并学习微信JS-SDK(1)

    <script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> 微信JS-SDK ...

  6. yii2源码学习笔记(十五)

    这几天有点忙今天好些了,继续上次的module来吧 /** * Returns the directory that contains the controller classes according ...

  7. PyQt5创建第一个窗体(正规套路)

    一.Pyqt5 创建第一个窗体 很多人写窗体程序都是直接敲代码,不使用设计器,我个人不是很赞成这种做法.使用设计器的好处是直观.维护方便,尤其开发复杂窗体的效率高. 但是每次修改ui文件后,需要重新生 ...

  8. MySQL在创建存储过程的时候,语法正确却提示You have an error in your SQL syntax

    我在使用MySQL工具编写MySQL存储过程的时候,明明语法正确,但是却一直提示You have an error in your SQL syntax. 比如下面一段代码 CREATE PROCED ...

  9. Codeforces Round #Pi (Div. 2)

    上次比完赛就准备写了, 结果懒癌发作了, 拖到了现在. Problem_A: 题意: 在一条x轴上有n座城市, 每个城市之间的距离就是它们对应坐标的距离, 现在求出每个城市到其他城市的最近距离和最远距 ...

  10. 应用git(SSH设置)

    git配置完成email和user后,才可以生成SSH Key. $ git config --global user.name "自定义名称" $ git config --gl ...