1. 程式人生 > >破窗理論、C++ 函式模板靜態庫

破窗理論、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 [email protected]  /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 [email protected]
#all:$(EXEC)
$(EXEC):$(OBJS)
        $(CC) $(CFLAGS) $(OBJS) -o [email protected] $(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>;

看來,搬磚還是要有實力才能搬得動,遇上問題才能漲知識!