实习点滴 - 破窗理论、C++ 函数模板静态库

最近在搬砖,本以为仅仅是体力活而已,无奈自己功力不够,处处碰壁。

这次的需求及其背景:

业务中有一个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>;

看来,搬砖还是要有实力才能搬得动,遇上问题才能涨知识!

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ActiViz是一个基于C#的开源数据可视化库,它提供了一系列用于创建和呈现2D和3D图形的功能。如果你想学习ActiViz,以下是一些学习点滴: 1. 理解ActiViz的基本概念:开始学习之前,了解ActiViz的基本概念是很重要的。了解ActiViz的工作原理、主要组件和使用方式,可以帮助你更好地理解和应用它。 2. 安装和配置ActiViz:在开始使用ActiViz之前,你需要将其安装到你的开发环境中。阅读官方文档或教程,按照指示进行安装和配置。 3. 学习ActiViz的API:ActiViz提供了丰富的API,用于创建和操作图形对象。学习这些API的用法和功能,可以帮助你更好地使用ActiViz来实现你的需求。 4. 创建基本图形对象:开始学习ActiViz时,从创建一些基本的图形对象开始是一个不错的选择。尝试创建点、线、多边形等基本图形对象,并学习如何对它们进行操作和渲染。 5. 了解数据可视化技术:ActiViz最常用的用途之一是数据可视化。学习如何使用ActiViz来可视化不同类型的数据,如二维数据、三维数据、图像数据等,可以帮助你更好地应用ActiViz来分析和展示数据。 6. 阅读官方文档和示例代码:ActiViz有详细的官方文档和示例代码,可以帮助你更深入地了解和使用ActiViz。阅读官方文档和运行示例代码,可以帮助你学习一些高级功能和技巧。 7. 参与开源社区:ActiViz是一个开源项目,有一个活跃的社区。参与到ActiViz的开发和讨论中,可以帮助你与其他开发者交流和学习,同时也可以为ActiViz的发展做出贡献。 希望这些学习点滴对你有帮助!祝你学***

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值