我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。
这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。
源码指引:github源码指引_初级代码游戏的博客-CSDN博客
一般我们会用虚函数或者会提供头文件就算会编写"接口"了。
不过接口有很多级别,提供头文件(没有约束的任意头文件)是最低的级别------这本来就是C/C++最基本的编码方法,通常只能在同一项目里,换个项目会遇到很多问题。
使用虚函数是高一点的级别,虚函数能够隐藏实现、提供动态类型,非常适合不同代码模块的协作。
如果要想编写一个通用的库,提供给别人使用,那么还得满足众多约束条件,才能实现一个真正能让别人用的库。
目录
[1 头文件必须是纯粹的(充分且必要)](#1 头文件必须是纯粹的(充分且必要))
[2 使用纯粹的C接口和C++虚函数](#2 使用纯粹的C接口和C++虚函数)
[3 默认隐藏所有符号](#3 默认隐藏所有符号)
[4 把普通类包装为接口](#4 把普通类包装为接口)
[5 兼容性考虑](#5 兼容性考虑)
1 头文件必须是纯粹的(充分且必要)
头文件不能包含多余的东西,也必须是完整的,不能有缺失。
出于编程方便,我们经常把include放在尽可能高的位置,比如一个公共的"common.h"或者"Util.h"或者"projectbase.h"之类的头文件里面,这样就不需要在每个源码文件里包含,省时省力(但是浪费编译器)。
但是作为库,我们应该只提供库接口,不应该包含多余的东西。包含只跟实现有关的头文件也不行,因为接口头文件里面用到的也必须同时提供,不然没法使用。
包含接口实现用到的标准头文件看起来没什么问题,标准头文件大家都有嘛,多数情形问题不大,但是如果别人系统上可能真的没有呢?或者要用不同的版本呢?或者包含了这个就会跟别的冲突呢?没必要制造麻烦。
有些零零碎碎的东西,比如实现用到的一个全局变量,是不是可以放在接口头文件里面?大家都方便嘛。不是的。头文件暴露的任何不必要的东西都会给使用者造成困扰,头文件包含的任何一个名字都可能和使用者自己的代码冲突。
原则:尽可能减少名字冲突的可能性
2 使用纯粹的C接口和C++虚函数
完全使用C接口是最理想的,配合extern "C"指定为操作系统的API标准,可以供任何语言使用(任何语言都有调用系统API的办法)。
如果使用C++暴露接口,那么应该遵循纯接口规则,只使用函数和虚函数,不使用数据成员。(我坚持认为interface没有进入C++标准是个悲剧)
C++类的内存布局是和编译器有关的,也和一些参数有关,比如为了对齐插入的填充字节,所以类里面如果包含数据成员,很可能带来麻烦。
任何数据成员的存在都是不必要的。内部的数据成员应该通过get/set暴露,这种做法给内部升级提供了可能,否则等于限制内部实现再也无法甩开这个数据成员。
任何私有方法的存在都是多余的。
不能有内联函数,内联函数实际上是重新编译的,根本不会用到库里面的(库里面很可能根本没有)。基于前面的约束,类里面没有数据成员,那么内联函数又能做些什么呢?
函数参数只使用基本数据类型,使用任何类都必须额外提供头文件,包含数据成员的类又存在潜在问题,所以不建议参数使用类。
原则:屏蔽实现依赖,仅仅通过接口交互
3 默认隐藏所有符号
这仍然是基于减少名字冲突这个原则。
windows上默认就是隐藏的,必须显示导出符号。
linux上则默认是导出的,这意味着存在巨大的冲突可能性,所以要查编译器手册,改为默认隐藏所有符号,再手动导出必要的符号。
4 把普通类包装为接口
把一个带有数据成员和私有成员的类包装为接口通常需要写一个包装类。
所有暴露的函数都要写一个函数来包装,这有点繁琐但是是必须的。
接口类通常自带一个创建自身的函数,一般叫instance()、Create类名()什么的。
5 兼容性考虑
头文件里最好不要有受编译器影响的东西,比如char,究竟是有符号还是无符号,如果是提供源码重新编译这可能不是问题,但是如果提供的是编译好的库,还是要斟酌一下的。
如果自己写库要同时编译成32位和64位,也要考虑是否需要使用int32_t这种明确指定的长度的类型。
(这里是文档结束)