C++ toolchain on windows and linux
1 | cl /I <include path> |
library path 加 C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib 主要為了 kernel32.lib。
不過很少直接用 cl,通常都是用 Visual Studio 整套 IDE。
1 | cl /I <include path> |
library path 加 C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib 主要為了 kernel32.lib。
不過很少直接用 cl,通常都是用 Visual Studio 整套 IDE。
self assignment:object 被 assign 給自己。
class member 有 pointer 時寫 operator=() 要小心處理 self assignment。如果先把自己原本的 member delete 掉,等同把 rhs 的 member 也 delete 掉,assign 後會得到爛掉的 pointer。
1 | Foo& Foo::operator=(const Foo& rhs) |
這做法可以處理 member pointer 但會讓 member pointer 指的位置經過 self assignment 後變得不同,另一種做法是檢查 this 是否跟 &rhs 相同,不同時才真的做 copy。
symbol 的 definition 可分為 strong symbol 跟 weak symbol。C/C++ 的 compiler 預設 function 及有初始化的 global variable 為 strong symbol,未初始化的 global variable 為 weak symbol。strong & weak symbol 跟處理 symbol 重複定義有關:
GCC 中可用 __attribute__((weak)) 來定義一個 strong symbol 為 weak symbol:
1 | __attribute__((weak)) int x = 2; // weak symbol |
1 | #include <iostream> |
weak symbol 可以在 link time 置換 function。一開始給個預設 implementation 並設為 weak symbol,使用者可以寫 function 編成 object file 去 link。由於使用者寫的是 strong symbol 會蓋掉原本的 default implementation,達到 link 階段換 implementation。
1 | #include <iostream> |
1 | #include <iostream> |
1 | > g++ -c weakfoo.cpp -o weakfoo.o |
link DLL 分成 implicit link 及 explicit link。
DLL library 需要 export 出 symbol,使用 DLL 的程式則需要 import symbol。VC 裡透過 __declspec(export) 來標示要 export 的 symbol,以及 __declspec(import) 標示要從外面 import 的 symbol。如果要讓 C++ 的 symbol 跟 C 相容,需要加 extern "C"(不做 C++ 名稱修飾)。
使用 library 的程式需要:
.lib,.lib 會在 build DLL 時一起 build 出來.dllbuild DLL 產生的 .lib 跟 static library 的不一樣,DLL 的 .lib 只是告訴使用的程式去哪找到 DLL,不會包含實際功能,所以檔案比較小。使用 implicitly link DLL 的程式必須要在 load 時可以找到 DLL,否則會跳錯誤訊息而且無法繼續執行。
作為 library 的 project 需在 VC 的 project properties→General→Configuration Type 設為 Dynamic Library (.dll)、Target Extension 設為 .dll。另外,如果 code 實際上沒有 export symbol,build DLL 時不會生出 .lib。
compile 時需 define FOO_DLL_EXPORTS。
1 | #ifndef FOO_H |
1 | #include "Foo.h" |
dumpbin 要從 VC 的 command prompt 才叫得出來。
1 | D:\tmp\FooLibrary\Debug>dumpbin /EXPORTS FooLibrary.dll |
project 的 include file 中須包含 Foo.h,link library 需有 FooLibrary.lib,執行檔旁則需放 FooLibrary.dll。
1 | #include <iostream> |
在 runtime 時才 load DLL。因為 runtime 才 load,即使 load DLL 失敗也可以在程式裡處理錯誤並繼續執行下去。
使用 library 的程式需要:
LoadLibrary() load DLLGetProcAddress() 取得想要的 function 的 addressFreeLibrary().lib,僅在執行時需要 .dll用 GetProcAddress() 需指定的 function name 是 library export 出來的 symbol,不是 library source code 裡的 function name。經過 C++ 名稱修飾,需要指定的 function name 會變得難以理解,這種 interface 應該沒人想用。除了 __declspec(dllexport) 外,export function symbol 的另一個做法是使用 .def 模組定義檔來宣告名稱。實際上是指定 alias 給原本的 symbol。
library source code 同上。
有加 extern "C" 的 Sub() 因為沒經過 C++ 名稱修飾,所以能直接用 function name,但 Add() 就得寫出 C++ 修飾後的 symbol name 才拿得到 function pointer。
1 | #include <iostream> |
刪掉 Foo.h 裡的 __declspec(dllexport)。
1 | LIBRARY FooLibrary |
1 | D:\tmp\FooLibrary\Debug>dumpbin /EXPORTS FooLibrary.dll |
main.cpp 的 GetProcAddress() 可以直接寫 Add 跟 Sub。
其實這是小實驗筆記吧…
最近看了些 code,發現不同情況下有不同 trace code 的方式,來 murmur 一下。
通常拿到一份 code 會先看它主要 component 的結構。如果有 UI 會先大概了解有哪些 component、分別叫什麼、彼此階層關係是什麼,例如哪個 container 裡放著什麼之類的。如果是網頁會先看資料夾結構是一般自己寫的還是用 framework。之後依照要做的事情不同而有不同的 trace 方式。
第一種,debug 或者找特定功能。
意識到這種 trace code 方式應該是大學在計中打工的時候,那時候想將 Wordpress 跟一個系統做簡單的整合連結,所以要找 Wordpress 裡相對應的功能。工作後 debug 也常常是用這種方式在看 code。
這方式是單看程式裡某一條路徑、某一段特定邏輯,除非發現是架構上的 bug 才會再往外擴。要是對那份 code 很熟,是也不用從最開始往下找啦……
第二種,想知道程式整體結構或運作之類的。
想全面但不深入細節的了解結構跟 high level 的邏輯概念。著重架構及概念,會看大致的流程邏輯,但不會細看每個 function 怎麼實作。這是最近演化(?)出來的方式。
main() 開始。select() 常常就是一個 while loop、塞一塞 fdset、call select()、後面依照 fdset 做事。某些 event loop 做法也有相似性。我覺得如果遇到關鍵字跟常見做法可以直接套用已知觀念,相對來說就會快很多,因為不需要特別再看實作去理解這部分在做什麼。有時候需要看實作是因為拼湊其他線索後還是不知道那段在幹嘛,只好透過實作細節重新抽象化成概念。
至於如何找 code?find all 與 grep --color -nr * 萬歲!(欸)
PS:寫這個是想知道自己怎麼 trace code 的,但怎麼寫一寫好像還是有點像要心領神會的難以言喻……Orz……
API 分很多種 style,各有優缺跟取捨。
這種分類方式是以 C/C++ 系列區分,但我覺得在其他語言方面也有類似概念。
一般 C function 們,通常會用 struct 跟一些命名規則來區別不同功能跟 component 等等。
類似的東西:PHP 那些 mysql_ 開頭的 function 們。
有時候使用者是用 C++ 開發,而 C style 的 API 希望能給這樣的使用者使用。在此狀況下,C style API 需要:
extern "C" 處理 C++ 與 C 之間 linkage 的問題name decoration,但 C 沒有。因此 C API 得用 extern “C” 包起來,讓 C++ compiler 知道這段應該要用 C style 的 link 方式。1 | #ifdef __cplusplus |
以物件導向為基礎的寫法。
類似的東西:PHP 的 mysqli 物件。
以 template 於 compile time 達到 static polymorphism──相同 interface 可支援多種 type,例如 STL。
相對物件導向的多型是在 runtime 做,使用 template 有較高的執行效率,但 compile 出來的檔案會比較大。因此,相對 code 大小較著重執行效能時可用 template,反之可用物件導向。
缺點之一是 template 的定義得放在 header,導致使用的程式需一起 compile template 的 header,但仍有些方式可以將 template 定義藏在 .cpp。另外,template 的 compile error 訊息很複雜,無論自己開發有什麼問題或者使用者使用時有何問題,複雜的 error message 讓人蠻頭大的。被害過…但至今還是沒搞懂…(欸)
interface 提供非常 general 的 function,實際上做什麼事由 input 決定。interface 大概像這樣:
Result DoCommand(string cmd, ArgList args)
Command() 內部 implement 會依據 cmd 決定要做什麼,args 則是對應 command 的參數,Result 則是回傳值。
例如 web service,使用者丟想做的命令跟參數給 web service,web service 再依據命令及參數回應。
由於 interface 非常 general,需要相對應的 general 的參數型態來傳這些參數,像上面的 Result。在不強調 data type 的語言(weakly type language)像 python 中很好處理,但在 C++ 裡就要自己寫或用其他 library 提供的了,如 Qt 的 QVariant。
好處是 API 修改非常彈性,幾乎不需要動到 interface。壞處是單從 interface 看不出 API 提供的功能,因為太 general 了,這時候只能靠文件。
C compiler 編出來的 object file 可以跟 C++ compiler 編出來的 object file link 在一起。
假設 Linux 環境下有一個 object file foo.o 是 C compiler 編出來的,可以將 foo.o 看作一 C library,而 main.cpp 是由 C++ 實作並且使用 foo.o 裡的 function,因此 main.cpp 會 include 內含 function declaration 的 foo.h。C++ compiler 在一般狀況下會將 foo.h 內的 function 當作 C++ function 以 C++ 的規則處理。
由於 foo.o 是 C compiler 編出來的,其對 function 的處理方式不像 C++ 會額外加修飾,使得 link 時 main.o 及 foo.o 中 symbol 對不起來而產生 undefined reference 錯誤。在 foo.h 中加上 extern "C" 是告訴 C++ compiler 要把這段 function declaration 當作 C function 處理,也就是不做 name decoration,之後 link symbol 才對得起來。實例如下:
1 | // foo.h |
1 | // foo.c |
1 | // main.cpp |
foo.o 的 symbol:
1 | > gcc -c foo.c |
一般狀況的 main.o symbol(重點在 18):
1 | > g++ -c main.cpp |
foo.h 有 extern "C" 的 main.o symbol:
1 | >readelf -s main.o |
盡量不要讓看過的書像船過水無痕,嘗試用自己的話做小結。
這章的重點在:What are the basic qualities of a good API?
API 是用來解決某個問題的,問題可大可小。
API 要能對問題提供抽象化的概念,並且能夠將這抽象概念以 interface 表達出來。以 C++ 來說,我的理解是用 class name 以及 function name 表達抽象概念,也就是使用 API 的人應該要能從 class 及 function 看出抽象概念及使用邏輯。
一個問題沒有絕對正確的抽象化方式,重點是 API 要保有某種一致性及邏輯。
如果用物件導向來做 modeling 就會用 object modeling,也就是會定出 object、每個 object 做什麼、object 之間的關係與互動。一個 class 應該要定義為「做什麼」(what)而不是「怎麼做」(how)。
隱藏所有實作細節。如果沒藏好,以後要改實作,使用者可能跟著改到崩潰然後就不想用你的 API 了。(無誤)
interface 訂得好,底下怎麼搞都沒關係,改 interface 比較會影響到使用者。
隱藏實作細節的方法:
PS:這裡說的使用者是指使用 API 的人,通常是其他 programmer 或自己,不是使用軟體的一般使用者。
API 功能要完整,可以滿足使用者需要的所有功能,要盡可能小但無法再更小了。
要小心 virtual function 可能會公開過多 function 給使用者。不過我還沒很懂這意思…
看到這裡總覺得在軟體設計上到處都有這句話,只是層次不太一樣。在 application 層級上,easy to use 希望的是一般使用者的好用。而在 API 層級上則是針對 API 使用者──通常是 programmer。
#if 跟 #ifdef,例如 #ifdef _WIN32。跟 OO 的原則一樣,class 之間、module 之間不要黏太緊。
簡單看兩個 component A 跟 B 黏得緊不緊的方式是看改了 A 後 B 會不會改很多、A 看得到 B 多少東西(如只看得到 public function 還是也看得到 private member 等等)。
要避免兩個 component 互相依賴變成 dependency cycle,不然想用 A 就一定要有 B,但邏輯上可能根本不需要 B,看起來就很怪。
這邊提兩種鬆散耦合的方法:Manager class 跟事件通知。
Manager class 會擁有並管理某些 class,例如與 output 有關的 class。
如果有一個 Manager class 管理 N 個 output class,其他 M 個 需要使用 output class 的 class 可以不需要跟所有 output class 都有關係(這樣會有 N * M 個關聯),大家可以只跟 Manager class 有關係(只剩 N + M 個關聯),從而降低 outpu class 與其他 class 間的耦合。
原來 Manager class 還有這種功能…
在「某件事發生時需要通知其他人」的情境下,有以下幾種方式可以降低通知者與被通知者的耦合:
這些方法可以讓通知方與被通知方彼此不需要知道對方是誰,否則雙方得知道對方是誰才能通知,而當兩邊各有很多 class 的時候,那根本是場災難……
老覺得跟 OO 的原則很類似,很多概念都有在其他地方看過。不過現在再看,似乎更能配合實際經驗加以理解。
像 naming、argument 順序、standard pattern 的使用、memory model、exception 的使用、error handling 等等,如果設計上有一致性,使用者較容易使用。保有一致性時,使用者可以輕易透過原本的理解認知推測新功能或其他部分如何使用。
在設計 C++ API 上,如果跟 standard library 保有相同的模式與規則也會讓 API 易於使用,畢竟大家都會用 standard library 啊!
雖然這篇標題是說 API 的設計,但小至 class 跟 function 也是有類似的觀念啊~
舉個 argument 不一致的例子:fscanf() 跟 fgets(),它們分別長這樣:
int fscanf(FILE* stream, const char* format, ...);char* fgets(char* str, int num, FILE* stream);
兩個類似的 function,FILE* stream 參數卻是一個在第一個、一個在最後一個,讓我太久沒用就得回頭瞄一下文件……
API 的功能可以是比較 atomic、比較基本的,讓外部使用這些基本功能組合出它想要的東西。也可以是 API 本身即組合一些基本功能、提供較方便的功能給外部使用。這兩種特性雖然互相衝突,但都是作為 API 希望滿足的。
滿足這兩種特性的方式是將僅提供基本功能的 core API 及較方便的 API 分開。可能是以 class 或檔案區隔,甚至直接拆成不同 library。重點是較方便的 API 是使用 core API 的 public interface,就像在 core API 外再包一層,而不會 access 到 core API 的內部結構。
如此使用者就能有方便使用的 API 也能在想自行組合功能的時候使用進階的 core API。這概念類似於 UI 設計上常有的「進階」按鈕──平常設定頁只會有基本設定,按進階按鈕後才會顯示更細部的設定。