制作函数模板静态库

C++模板的学习会遇到各种各样的问题,对于一个某种程度上的新手而言,难免会碰到一些问题。但泛型编程拥有着“双拳敌四手”的绝妙心法,威风八面,实在也让自己按捺不住。前些天自己一次对reverse模板的实现过程让自己体会到解决问题的乐趣,所以如今每每遇到问题就会尝试着去探个究竟,有时候自觉也陷落于诸多语法的细枝末节当中,好在学习模板当前仅是一项技术积累,并无工程进度要求,所以暂且细细为之。

1. 起因

equal, search, find, find_if, copy, remove_copy, remove_copy_if, remove, transform, partition, accumulate,这些都是STL中的算法成员,这次自己是一个一个予以实现并且进行简单的测试。在测试的过程当中,自己发现每次总是要去编写一个函数模板专门用来打印容器当中的元素,于是便想到是否可以把它也做成一个静态库,或者高级点,制作成一个动态库。

 1 #include <iostream>
 2 #include <vector>
 3
 4 template <class IN> // 这一部分做成静态库
 5 void PrintByIterator(IN begin, IN end)
 6 {
 7     while (begin != end)
 8     {
 9         std::cout <<*begin <<" ";
10         ++begin;
11     }
12     std::cout <<std::endl;
13 }
14
15 int main()
16 {
17     int testArr[5] = {1, 3, 5, 9, 2};
18     PrintByIterator(testArr, testArr + 5);
19     return 0;
20 }

为了便于笔记主题的清晰演示,上面给出了一个简单的代码示例,通过它来演示本笔记所要达成的制作函数模板静态库的目的。如上代码片段当中的函数模板PrintByIterator,如果能够将其制作成为一个静态库,就可以避免在实现其他函数的同时重新编写或者复制拷贝的工作。当然,也存在有其它需求场景。

2. 方案探索

按照库的制作流程,我将测试工程的代码重新进行了组织,主要思路是按照常见非模板函数的静态库制作流程,分为两部分:

  • 静态库部分:将PrintByIterator模板的定义单独新建静态库工程
  • 原工程部分:通过头文件以接口的方式对PrintByIterator进行调用
 1 // 静态库工程,包含PrintByIterator模板定义
 2
 3 #include <iostream>
 4 template <class IN>
 5 void PrintByIterator(IN begin, IN end)
 6 {
 7     while (begin != end)
 8     {
 9         std::cout <<*begin <<" ";
10         ++begin;
11     }
12     std::cout <<std::endl;
13 }
 1 // 测试工程 - cprint.h
 2
 3 #ifndef __CPRINT_H_
 4 #define __CPRINT_H_
 5
 6 template <class IN>
 7 void PrintByIterator(IN begin, IN end);
 8
 9 #endif // __CPRINT_H_
10
11
12 // 测试工程 - main.cpp
13 #include <iostream>
14 #include "cprint.h"
15 int main()
16 {
17     int testArr[5] = {1, 3, 5, 9, 2};
18     PrintByIterator(testArr, testArr + 5);
19     return 0;
20 }

然而,如上的代码组织方式,在编译工程的时候会出现如下的错误提示:

||=== Build: Debug in TemplateLibraryTest (compiler: GNU GCC Compiler) ===|

obj\Debug\main.o||In function `main‘:|

F:\Coding\C++\Chapter8 Template\TemplateLibraryTest\main.cpp|8|undefined reference to `void PrintByIterator<int*>(int*, int*)‘|

||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

该编译错误的信息说明,当前工程在链接阶段找不到针对PrintByIterator针对int *的实例化版本。在当时,我没有想到是否是模板的编译本身的原因,而是去猜想该错误是否是因为静态库的制作与使用出现了问题。因此,我便再次将库的相关知识学习了一番,直到发现关键的问题在何处,下面对整个问题的推演与剥离过程简要进行描述。

2.1 重温“静态库”

对于静态库和动态库的制作,这里有一篇介绍颇为详细的笔记。静态库的优点之一也就是可以复用代码,提高开发效率。不过,它的优点远不止于此:

  • 一种生态环境的组件:通常操作系统会通过库的形式提供API
  • 复用代码:库通常是对一些常见功能的封装,比如编译器携带的运行时库
  • 设计上的考虑:通过静态库的组织形式可以将函数的定义与实现相分离,在合适条件下对于库的使用者隐藏具体实现
  • 利于项目管理:通过将模块化,除了可以进行权限管理之外,还可以通过库的组织形式提高工程build效率

如上,静态库给我们提供了诸多的好处,但依然有着优化的空间。比如可执行文件当中的多个相同拷贝、库更新时候需要整个工程重新编译。这个时候,产生了动态库技术,它很好的弥补了静态库的这些不足:

  • 没有静态库bin/exe文件空间浪费的缺点
  • 不需要在库更新之后整个工程重新编译和发布
  • 具有静态库的优点

静态库的构建过程简略如下:

可见,静态库是在链接阶段进行链接,回到上面的问题,看起来如上的做法似乎并无不对的地方。如果按照通常的函数静态库对提示的错误进行类比推测:错误出现在链接阶段,也就是说明了在自己制作的动态库当中根本没有包含有对应的目标代码。那是不是因为模板的编译与通常函数的编译有不一样的地方呢?

2.2 模板编译的特殊性

编译模板函数与编译非模板函数的确有不一样的地方,标准C++为编译模板代码定义了两种模型:包含编译模型与分离编译模型。

  • 包含编译模型

在包含编译模型当中,编译器必须看到所有模板的定义。一般而言,可以通过在声明函数模板的头文件中添加一条#include指令将模板定义包含到头文件中。

  • 分离编译模型

在分离编译模型中,必须启用export关键字来跟踪相关的模板定义。export关键字能够指明给定的定义可能会需要在其他文件中产生实例化。

编译器对如上编译模型的实现各有不同,当前而言几乎所有的编译器都支持包含编译模型,部分编译器支持分离编译模型。同时的,每一种编译器在处理模板实例化时的优化方式均各有不同。基于这两点,必要时只有去查阅编译器的用户指南去了解个究竟。对于分离编译模型,我当前使用的CodeBlocks 13.12并不支持,因为无法识别关键字export。

CodeBlocks13.12 版本不支持export关键字

可见,模板本身是C++中一个非常好的特性,而编译器对其的支持决定了它的与众不同——模板定义与声明不能分开放置。对于函数模板来说,编译器看到模板定义的时候,它不立即产生代码。只有在看到使用函数模板时,编译器才产生特定类型的模板实例,相当于生产对应类型的代码。也就是,在上面的静态库构建图示当中,模板的实例化过程可以抽象为在“编译”阶段完成。所以它也被称为编译期多态

对于函数调用而言,编译器只需要看到函数的声明,在链接阶段将对应的函数实现目标代码链接到一起即可。但是,对于模板函数,在编译时,编译器看到函数模板的声明时根本还没有生成对应的代码,直到看到使用模板函数时才开始进行类型推导并根据具体的类型生成具体的实例函数,比如生成对应的compare<int>(), compare<double>代码。这个推演函数模板类型,并且实例化对应类型的函数的整个的过程也就是通常笼统的被称为模板实例化的过程。

上面这种将两个过程合成为实例化过程的想法,容易让我们形成一种想法:“编译函数模板时必须要见到函数模板的定义。”如果抱有这种想法,那么也就意味着为函数模板制作静态库是不可能的。我自己当时也是如此以为,自以为是自己给自己挖了一个坑。直到我查找到一份PPT资料,才把类型推演与具体类型函数生成两个过程区分开来,这也就可以让静态库的制作成为可能。

3 解决方案

有了之前的分析过程,便可大致了解模板的整个构建过程,可以得知最为关键的问题也就是:在链接阶段提供具体类型函数的那部分代码即可。

方式一:

 1 // cprint.h
 2 #ifndef __CPRINT_H_
 3 #define __CPRINT_H_
 4 template <class IN>
 5 void PrintByIterator(IN begin, IN end)
 6 {
 7     while (begin != end)
 8     {
 9         std::cout <<*begin <<" ";
10         ++begin;
11     }
12     std::cout <<std::endl;
13 }
14 #endif // __CPRINT_H_
15
16 // main.cpp
17 #include <iostream>
18 #include <vector>
19 #include "cprint.h"
20
21 int main()
22 {
23     int testArr[5] = {1, 3, 5, 9, 2};
24     PrintByIterator(testArr, testArr + 5);
25
26     return 0;
27 }

这是第一种方式,也即是将函数模板的实现放在头文件当中。这个时候类型的推演和int *类型函数的生成可以统一被视作一个“实例化”的过程。

方式二:

 1 // cprint.h
 2 #ifndef __CPRINT_H_
 3 #define __CPRINT_H_
 4
 5 #include "cprint.cpp"
 6
 7 #endif // __CPRINT_H_
 8
 9 // cprint.cpp
10 #include <iostream>
11
12 template <class IN>
13 void PrintByIterator(IN begin, IN end)
14 {
15     while (begin != end)
16     {
17         std::cout <<*begin <<" ";
18         ++begin;
19     }
20     std::cout <<std::endl;
21 }
22
23 // main.cpp
24 #include <iostream>
25 #include <vector>
26 #include "cprint.h"
27
28 int main()
29 {
30     int testArr[5] = {1, 3, 5, 9, 2};
31     PrintByIterator(testArr, testArr + 5);
32     return 0;
33 }

这种方式在头文件当中包含函数模板的实现文件,与第一种方式实质是一样的,也就是对于函数模板的使用者来说,它可以同时看见函数的声明与定义。

方式三:

 1 // cprint_ins.cpp
 2 #include <vector>
 3 #include "cprint.cpp"
 4
 5 using std::vector;
 6
 7 template void PrintByIterator<int *>(int * a, int * b);
 8
 9
10 // cprint.cpp
11 #include <iostream>
12
13 template <class IN>
14 void PrintByIterator(IN begin, IN end)
15 {
16     while (begin != end)
17     {
18         std::cout <<*begin <<" ";
19         ++begin;
20     }
21     std::cout <<std::endl;
22 }
 1 // cprint.h
 2 #ifndef __CPRINT_H_
 3 #define __CPRINT_H_
 4
 5 template <class IN>
 6 void PrintByIterator(IN begin, IN end);
 7
 8 #endif // __CPRINT_H_
 9
10 // main.cpp
11 #include <iostream>
12 #include <vector>
13 #include "cprint.h"
14
15 int main()
16 {
17     int testArr[5] = {1, 3, 5, 9, 2};
18     PrintByIterator(testArr, testArr + 5);
19     return 0;
20 }

这种方式最开始看起来比较怪异,但也是本文最终所选定的最终方案,通过对它们的合理组织,便可以使得在链接阶段提供针对int *类型函数的实例化代码。其实现是通过在cprint_ins.cpp当中已经将模板PrintByIterator进行了显示的实例化操作,所以可以将它们作为库的形式提供,在链接阶段也就可以提供针对int *的实例化版本。如此便可使得静态库的制作得以实现。

<完>

时间: 2025-01-16 15:05:47

制作函数模板静态库的相关文章

如何制作自己的静态库

如何制作自己的静态库 将一些不想暴露给使用方的实现代码打包成.a库(比如:百度地图sdk,写sdk的时候,需要使用到) 需要创建一个静态库的工程,来实现代码逻辑,并完成对代码的打包(.a库) 1.创建静态库工程 创建一个测试工程(使用.a库)(将.a库和必要的头文件导入到测试工程) 静态库的打包(1.基于模拟器生成的.a库,此.a库是基于电脑的cpu(i386)逻辑生成,无法在真机上使用 基于真机生成的.a库(armv6,armv7智能手机cpu逻辑)无法在模拟器环境下使用) 将两个静态库合并成

xcode6制作IOS .a静态库小记

创建iOS静态库 简单写个打印的代码 编码完成之后,直接Run就能成功生成.a文件了,选择 xCode->Window->Organizer->Projects->Your Project, 打开工程的Derived Data目录,这样就能找到生成的.a文件了,如图 静态库就生成了 如果你要导入静态库,有三种方式 第一种: 是把include 和 .a通过Add Files to "MyLib"方式加入工程 等同 加到target工程->Build Pha

iOS中静态库的制作——使用创建静态库项目的方式创建静态库

最近公司要求写SDK,我就想把它弄成静态库的方式 我的理解:所谓静态库,就是把所有的.m文件打包成一个.a文件,这样使分享代码的时候更加简洁,重要的是别人也不会看到你.m文件中的傻B代码了 环境是Xcode6.2 iOS8.2 首先,创建一个静态库项目 删掉Xcode自动创建的同名文件,然后导入你需要做成静态库的文件 在这里我导入一个简单的输出字符串的文件 然后选择运行的设备进行编译,这里我有不理解的地方:在Xcode6.2中,当我首先选择模拟器,然后编译文件的时候,.a文件依然是红色的,说明静

静态库的制作详解

静态库的制作 1   静态库的存在形式: .a 和.framework 动态库的存在形式:.dylib 和.framework 2   静态库和动态库的区别: 静态库在链接的时,会被完整的复制到可执行文件中,被使用多次,就由多份拷贝. 动态库则不会被复制,只有一份,程序运行时,动态加载到内存,系统只加载一次,多个程序共用 但是:程序中如果使用自己的动态库是不允许上架的. 3   使用静态库,可以保护核心代码,将MRC的项目打包成静态库,就可以在ARC环境下直接使用. 4   静态库的特点: .a

ios中静态库的创建和使用、制作通用静态库(Cocoa Touch Static Library)

创建静态库可能出于以下几个理由: 1.你想将工具类代码或者第三方插件快捷的分享给其他人而无需拷贝大量文件.2.你想让一些通用代码处于自己的掌控之下,以便于修复和升级.3.你想将库共享给其他人,但不想让他们看到你的源代码. Xcode6创建静态库详解(Cocoa Touch Static Library) 一.创建静态库文件 打开Xcode, 选择File ----> New ---> Project. 新建工程. 选择iOS ----> Framework & Library -

ios 静态库的制作

废话不多说直接上操作 原理在后面 在平时开发中,可能几个公司合作一个项目,但又不想让另一个公司看到自己的源代码,怎么办.这时我们就可以制作自己的静态库. 如何制作.a 新建项目:选择cocoa touch static library 把你需要制作静态库的源码添加到工程中 选择你要公开的.h 文件 点击build Phases ->左边那个"+" ->new headers phases后将你要公开的.h 文件拖入到public 中 编辑edit scheme,选择是rel

【iOS开发-115】静态库的制作以及第三方框架iOS Universal Framework,DEBUG和RELEASE

(1)概念介绍 --我们平时在项目中用的最多的就是开源的第三方库,这种库是开源的,我们不仅能用,还能查看源代码甚至可以修改源代码. --与开源库对应的就是闭源,闭源库分为动态库和静态库.动态库就是.dylib或者.framework结尾的文件.就是苹果官方提供给我们用的那些库.开发者不能在项目中使用自制的动态库,否则无法上传到APPStore. --所以,对于闭源库,我们主要讨论的是静态库.静态库的样子就是用户拿到的文件有很多头文件.h+资源包+编译过的一个二进制文件.a(.framework)

Windows 下VC++6.0制作、使用动态库和静态库

Windows 下VC++6.0制作.使用动态库和静态库 一.VC++6.0制作.使用静态库 静态库制作 1.如图一在VC++6.0中new一个的为win32 static library工程并新建一个.cpp和一个.h(C++header file)文件 2..cpp程序直接照老师给的打,注意这里需要改错,去掉(long) 3. .h文件需要自己编写格式如下.以head.h为例 #ifndef _HEAD_H_ #define _HEAD_H_ unsigned long unsgn_pow(

Linux编译过程与动静态库制作

一.Linux编译过程 预处理->编译->汇编->链接 二.预处理 作用: 宏展开 头文件包含 条件编译 布局控制,如#pragma:添加行号,方便后期问题查错. 编译命令 gcc -E  *.c  -o  *.i 三.编译 作用: 将预处理生成的代码进行词法.语法与语义进行解析,生成汇编代码. 命令: gcc -S *.i -o *.s 四.汇编 作用: 将汇编代码进行处理,转换成计算机能识别指令集,生成目标文件(.o/.obj). 命令: gcc -c *.s -o *o 五.链接