最近在搬砖,本以为仅仅是体力活而已,无奈自己功力不够,处处碰壁。
这次的需求及其背景:
业务中有一个recommendId的东西,类型是string或者vector< char >。
第一个字节(即recommendId[0])用来标识数据所属的类型,比如4代表商业化广告,5代表游戏广告等,然后后面的字节或者是string类型的数据,或者是jce类型的数据(这个即为编码协议)。
最近开始推广使用新的协议,第一个字节为7,代表新协议,后面的数据是TLV格式的数据,即 [7 tlv tlv …]。
(TLV 的意思:Type 类型, Lenght 长度,Value 值; Type 和 Length 的长度固定,Value的长度由Length指定)
第一个字节为7用来跟旧协议区别开,然后后面的TLV数据中,type用来标识原先的商业化广告、游戏广告等。
原先上报数据的逻辑是对recommendId的第一个字节进行判断:
if (ADVERTISEMENT == recommendId[0])
{
item.needReport = 1;
}
现在需要对代码进行修改,以兼容新的协议。
于是新的代码大致如下:
if ( !recommendId.empty() )
{
//旧协议
if (ADVERTISEMENT == recommendId[0])
{
item.needReport = 1;
}
else
{
if (7 == recommendId[0] && recommendId中包含type是商业化广告的数据)
{
item.needReport = 1;
}
}
改一下代码十分简单,比较痛苦的是,这个修改涉及到10多个服务,需要修改的地方有几十处。
人的懒惰真的是很有惯性,一开始我按照上面的方法,复制、粘贴、看情况修改下变量名,一处一处地改过去。
当发现需要改的地方太多,才觉得其实应该把这一块逻辑独立出来成一个公共函数的,不然以后再有变动,又要一处处地去改。然而又想,我都已经这样子改了将近一大半了,如果要重构,前面的修改都白费了。还是算了吧,以后又不一定会再改这里了,就算再改,也不一定是我改了。然后硬着头皮继续一处处改下去,花了半天终于改完了。
饭后闲暇,刷了一下KM(公司内部类似知乎的平台),看到一个关于代码重构的问题,下面一句评论很精辟:“根据破窗理论,除非遇上一个对代码有洁癖的码农,或者一个推动能力很强的leader,否则代码很难有重构之日。”
想想自己刚刚干了什么,我可不想污染代码!所以决定打自己脸,刚改完就开始走上重构之路——把判断的逻辑独立出来,以后任何修改都只需要改一个地方而不是几十个地方。
独立出来之后,放到公共库里面,打算做成一个静态库。
相关知识:
Linux的静态库以.a结尾,动态库以.so结尾。
连接静态库有两种方法:
一、在编译命令最后直接加上库路径/库名称。
例如你的库在绝对目录/lib/libtest.a下面:
$(CC) $(CFLAGS) $^ -o $@ /lib/libtest.a
二、用-L指定库的路径,用-l指定库的名称。
例如库的名称为libbluetooth.a 那么就用-lbluetooth (去掉前缀lib和后缀.a)
CROSS_COMPILE = arm-linux-uclibc-
CC = $(CROSS_COMPILE)gcc
EXEC = armsimplescan
OBJS = simplescan.o
CFLAGS = -Wall -I/home/user/blueZ/bluez_arm/bluez-libs/include
LDFLAGS = -L/home/user/blueZ/bluez_arm/bluez-libs/lib -lbluetooth
#default:$(EXEC)
%.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
#all:$(EXEC)
$(EXEC):$(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS) -static
clean:
rm -f $(EXEC) $(OBJS)
上面的Makefile中$(LDFLAGS)要放在$@的后面,不然不会起作用。
制作静态库、动态库的方法:
OBJS = foo.o
libtest.a : $(OBJS)
rm -f libtest.a
$(AR) rcs libtest.a $(OBJS)
libtest.so : $(OBJS)
rm -f libtest.so
$(CC) -shared -o libtest.so $(OBJS)
使用库:
对于使用libtest.a 和 libtest.so
LIB += -L/lib
LIB += -ltest
target: $(OBJS)
$(CC) -o target $(CFLAGS) $(OBJS) $(LIBS)
如果动态库和静态库都存在那么会优先链接动态库,如果找不到动态库,就直接使用静态库。
如果为了调试要强制使用静态库,可以在CFLAGS中加入-static。
C++ 函数模板静态库
本来以为一切大功告成,结果服务编译失败——前面说过,recommendId可能为string也可能为vector< char >,而我的函数中使用的是string。
首先想到的第一个方案:重载该函数,接受参数类型为vector< char >的参数。然而这个方案使得同一份代码出现了两次——这是不能接受的。
第二个方案就是使用模板,这样子就可以自动推导实际的参数类型了。
于是写出了类似下面的代码:
//test.h
template <typename T>
bool isAdvertisement(const T& recommendId);
______________________________________________________
//test.cpp
#include "test.h"
template <typename T>
bool isAdvertisement(const T& recommendId)
{
//具体实现
//...
}
编译服务的时候出现:
undefined reference to `bool isAdvertisement<string>(const T& recommendId)(string const&)
该编译错误的信息说明,在链接阶段找不到isAdvertisement针对string const&的实例化版本。
查到的相关资料如下:
模板编译的特殊性
编译模板函数与编译非模板函数的确有不一样的地方,标准C++为编译模板代码定义了两种模型:包含编译模型与分离编译模型。
包含编译模型
在包含编译模型当中,编译器必须看到所有模板的定义。
一般而言,可以通过在声明函数模板的头文件中添加一条#include指令将模板定义包含到头文件中。
分离编译模型
在分离编译模型中,必须启用export关键字来跟踪相关的模板定义。
export关键字能够指明给定的定义可能会需要在其他文件中产生实例化。
编译器对如上编译模型的实现各有不同,当前而言几乎所有的编译器都支持包含编译模型,部分编译器支持分离编译模型。同时的,每一种编译器在处理模板实例化时的优化方式均各有不同。基于这两点,必要时只有去查阅编译器的用户指南去了解个究竟。
模板本身是C++中一个非常好的特性,而编译器对其的支持决定了它的与众不同——模板定义与声明不能分开放置。
对于函数模板来说,编译器看到模板定义的时候,它不立即产生代码。只有在看到使用函数模板时,编译器才产生特定类型的模板实例,相当于生产对应类型的代码。也就是,在上面的静态库构建当中,模板的实例化过程可以抽象为在“编译”阶段完成。所以它也被称为编译期多态。
相对于一般函数调用而言,编译器只需要看到函数的定义,就会直接编译对应的代码,之后在链接阶段将其与引用它的目标代码链接到一起。
对于函数模板,在编译时,编译器看到函数模板的定义时根本不会生成对应的代码,直到看到使用函数模板时才开始进行类型推导并根据具体的类型生成具体的实例函数,比如生成对应的compare(), compare代码。这个推演函数模板类型(过程1),再实例化对应类型(过程2)的函数过程也就是被称为模板实例化的过程。
解决方案
根据上面分析,可得知最为关键的问题是:在链接阶段提供具体类型函数的那部分代码。
方式一:
将函数模板的实现放在头文件当中。这个时候类型的推演和具体类型函数的生成可以统一被视作一个“实例化”的过程。
方式二:
在头文件当中包含函数模板的实现文件(也就是.h中include了.cpp,然后再无其他内容),与第一种方式实质是一样的,也就是对于函数模板的使用者来说,它可以同时看见函数的声明与定义。(这个方式不推荐,在.h中include .cpp文件,很奇怪=。=)
方式三:
显式实例化。
//test.h
template <typename T>
bool isAdvertisement(const T& recommendId);
______________________________________________________
//test.cpp
#include "test.h"
#include <string>
template <typename T>
bool isAdvertisement(const T& recommendId)
{
//具体实现
//...
}
template <> bool isAdvertisement<std::string>(const std::string& recommendId); //特化,显示的实例化操作
//对于类来说,则是如下的形式
template class Test<int>;
看来,搬砖还是要有实力才能搬得动,遇上问题才能涨知识!