在使用 Google C++ Mocking Framework 的時候很可能遇到一個情況,當原本的 virtual function 的 prototype 改變的時候,你所 mocking 的 prototype 很可能不是預期會被呼叫的那個。
Continue reading
Tag Archives: C++
一個 boost::exception 的例子
因為覺得 dprintf 出現的次數實在太多了,我會希望在 exception 就可以包好 __FILE__ 跟 __LINE__,又可以把 std::wstring 包在 exception 一起丟出去,在 catch 的時候再印出來就好。
所以試著寫了這樣一個例子,結果寫的四不像,跟原本想的東西完全部不同了,而且我對於那個 BUFSIZ 相當不滿啊。其實原本希望可以用 ##__VA_ARGS__ 這種 macro 直接吃進來變成一個 boost::any[],然後再想辦法轉成 boost::format 可以用的格式,中間還可以透過 BOOST_FOREACH 來玩的,不過我徹底失敗了,所以最後的成果大家笑笑就好了。 Continue reading
從 inet_ntoa 看 thread safe 的 API
這周 refactor 一段 code 之後,被同事說:「為什麼 log 的來源 IP 跟目的地 IP 總是一樣呢?」
char *szDestIp = inet_ntoa(destAddr); char *szSrcIp = inet_ntoa(srcAddr);
後來仔細想了一下 inet_ntoa 的 prototype,我就明白了!如果這個 function 不必特意去 free 回傳的 pointer,那可能在內部有一個 static buffer 去保存這個值。果不期然,FreeBSD 的原始碼是這樣實作的
/*const*/ char *
inet_ntoa(struct in_addr in) {
static char ret[18];
strcpy(ret, "[inet_ntoa error]");
(void) inet_ntop(AF_INET, &in, ret, sizeof ret);
return (ret);
}
char *
inet_ntoa_r(struct in_addr in, char *buf, socklen_t size)
{
(void) inet_ntop(AF_INET, &in, buf, size);
return (buf);
}
到這邊我們大概就知道,inet_ntoa 並不是一個 thread safe 的 function,你可以用下面這段程式測一下會發生什麼事情。
void* thread_func (void *param)
{
char * addr= (char *)param;
struct in_addr val;
inet_aton(addr, &val);
while (1)
printf("%s => %s\n", addr, inet_ntoa(val));
pthread_exit(NULL);
}
int main()
{
pthread_t thr1, thr2;
pthread_create(&thr1, NULL, thread_func, (void *)"127.0.0.1");
pthread_create(&thr2, NULL, thread_func, (void *)"127.0.0.2");
while(1) {
sleep (1);
}
return 0;
}
輸出大概會像
127.0.0.2 => 127.0.0.1
127.0.0.1 => 127.0.0.2
127.0.0.1 => [inet_ntoa error]
127.0.0.2 => 127.0.0.2
所以,直接使用 inet_ntoa 在 reentrant 上可能會發生問題,如果你必須面對這些問題,最好的方式就是使用 inet_ntoa_r 這個 reentrant 的版本,或者直接使用 inet_ntop。
而這難道非得要踩到地雷,或者從 API 的細節才能窺知這一切嗎?其實並不完全是,當你發現回傳的是一個 char *,又沒有要求你特別 free 他,你是不是就該猜測他是回傳 function 內部的一個 static buffer 了呢?所以,舉凡 ctime, asctime 都不保證是 thread safe 的,這是我們 programmer 該留意的細節。
所以,也許我們以後需要更小心的去寫程式,在選擇 API 時也必須留意更多細節,以免未來踩到更多地雷……。
以 boost::posix_time 處理時間字串
其實標題並不是那麼準確,這點我們之後再談。
其實起源很簡單,我們常常需要從各式各樣的時間字串轉成 time_t 或者 struct tm,要不然還可能是 FILETIME 跟 SYSTEMTIME 之類的結構。個人覺得 parsing 時間是一件難搞的事情,尤其是當你還要考慮 timezone 的影響,一切都變得不是那麼簡單了。
首先我必須要說,在 Windows 中沒有 strptime(3) 這樣還算好用的東西可以用,所以我又把腦袋動到 boost 上面去了。
我的目標是正確的處理「Sat, 28 Mar 2009 20:18:32 +0800」這樣的字串,然後把他轉成 time_t。我們會分兩個部份處理,首先我們先處理「Sat, 28 Mar 2009 20:18:32」這個部份,因為目前的 %q 只能處理輸出部份,請參考 Date Time Formatter/Parser Objects。
using namespace boost::posix_time;
using namespace boost;
using namespace std;
std::string strDateTime = "Sat, 28 Mar 2009 20:18:32 +0800";
std::string format = "%a, %d %b %Y %H:%M:%S";
ptime pt(not_a_date_time);
time_input_facet * input_facet = new time_input_facet(format);
stringstream ss(strDateTime);
ss.imbue(locale(ss.getloc(), input_facet ));
ss >> pt;
這樣的確很簡單吧,只要按照文件寫的把 format 寫好,boost 就能幫你從 stringstream 的字串轉成 posix_time。
而 timezone 的部份就需要噁心一點的作法了。
std::string strHour, strMinute;
ss >> setw(3) >> strHour >> setw(2) >> strMinute;
time_duration td(lexical_cast< int >(strHour), lexical_cast < int > (strMinute), 0);
pt -= td;
我利用 setw 分別把 timezone 的小時與分抓出來,並轉成 time_duration,讓 posix_time 能直接減掉 timezone 的 offset 成為 GMT time,之後再做點手腳就可以變成 time_t 了。
嚴格來說,我想我是透過 time_input_facet 來處理字串的,希望這點小技巧對於需要 parsing 時間字串的人能有所幫助。
p.s: 其實這篇三個禮拜前就準備好了,只是一直拖到現在才把他丟出來 XD
初探 TR1 function object
最近的專案常常需要更新 INI 設定檔,而最麻煩的事情莫過於太多零散的設定,你必須要記住 INI 檔設定的 key,再把在某個 object 的 member function 把值抓出來,最後再丟給 WritePrivateProfileString 之類的 function 把值更新進檔案。
所以常常就會寫出一堆噁心的 code 像是。
obj.iniKey = "INI_SERVER";
obj.iniValue = info.GetServer();
UpdateByObj(obj);
這樣的話,有幾個設定就要寫幾次,所有的 Key 跟 Value 的相關性都顯得零散,未來要加上新的設定,也非得要再加上類似的 code 才行……。
忽然我就靈機一動,也許可以用 tr1::function 跟 tr1::bind 來做這樣的事情。
struct proxyConfig
{
std::string iniKey;
std::tr1::function<std::string()> func;
};
const proxyConfig configArray [] =
{
{ "INI_SERVER", std::tr1::bind(&proxyInfo::GetServer, &info) },
{ "INI_PORT", std::tr1::bind(&proxyInfo::GetPort, &info) },
{ "INI_USER", std::tr1::bind(&proxyInfo::GetUser, &info) },
{ "INI_PASS", std::tr1::bind(&proxyInfo::GetPass, &info) },
};
for (size_t i = 0; i < sizeof(configArray)/sizeof(configArray[0]); ++i)
{
obj.iniKey = configArray[i].iniKey;
obj.iniValue = configArray[i].func();
UpdateByObj(obj);
}
靠著把 INI 的 key 跟 member function 建表就顯得簡單一點,並把更新的程式碼簡化成一份,未來如果要新增不同的設定,也只要更動表中的 INI key 跟所綁定的 function 就好。
不過後來仔細想想,其實只要在建表的時候寫成 info.GetServer() 就可以滿足我的需求了。
而 tr1::function 與 tr1::bind 絕對不是只有這點小技倆而已,真正使用的巧妙應該是 C++ Function Objects in TR1 所提及那種彈性而優雅地將 function 包裹成物件,能有改變的彈性,也能應時應地做出適合的調整。
如果你的編譯器還不支援 tr1,除了換一個之外,你還可以考慮一下 boost,也許更瞭解一點,會讓你的生活變得更好歐。
C++ Format String using boost::format
如果你只想寫純粹的 C,那你可以忽略這篇的資訊了,因為 sprintf 之類的 function 大概就可以滿足你了。那為什麼還要提到 C++?我想 C++ 的 string 對於程式設計者是美好的,因為我們不用擔心 buffer 到底需要多長,我們可能是無腦的利用 operator += 去操作這個字串,並利用提供的 substring 等 function 快速的開發軟體。
但在我甚少的 C++ 開發經驗中,我總覺得透過 operator += 以及 stringstream 等東西,並沒有辦法像 sprintf 那樣直覺而優雅的將變數置換至字串當中。
就以 Windows 常見的 INI 格式來說,我們如果要輸出一個 AppName,像是 [MyApp],可能的作法是
output += '[';
output += strSection;
output += ']';
這似乎比傳統的 sprintf 要顯得複雜的多,也顯得不好閱讀……
sprintf(output, "[%s]", strSection);
現在你可以考慮 boost::format
std::string output = boost::str(boost::format("[%s]") % strSection);
讓你寫的 C++ 能有 sprintf 的優雅性,又不必太過於擔心 buffer 的操作。
至於一些 manipulators,與詳細的 formatting 的用法,就請你自己去看一下官方的說明吧。此外,我覺得 python 的 formatting string 能吃 dictionary 也實在是很棒,而且很優雅的寫法。
print "%(key)s %(key)s" % {'key': 'yoyo'}
在 boost::format 雖然只能這樣寫,但也聊勝於無了。
std::cout < < boost::format("%1% %1%") % "yoyo";
某些情況下,你會考慮用 boost::lexical_cast,而捨棄 stringstream 等,不過也容許我提醒你一下,boost 會讓 object 變得肥上不少,至於怎麼取捨,就端看你的選擇了。