小心繼承 iostream 的邊際效應 (side effect)
今天搞了一個下午,結論就是這個教訓:小心繼承 iostream 的邊際效應 (side effect)。白話一點,就是不要在與 I/O 不相干的地方偷懶對 C++ iostream 使用繼承。
去年年中寫了一個簡單的小 database wrapper 程式庫,一樣是因為不方便使用四處可見的既有 library,所以得自己搞。裡面有一個 class 叫做 Statement,我希望它的用法就跟 cout 一樣,因為數字在 SQL 指令裡,一樣是以字串的形式呈現,另外,這樣也有機會可以依據傳入的型別的不同,自動決定要不要加 SQL 引號,以及要不要 escape 掉插入內容的引號。既然用法就像 cout 一樣,那就乾脆繼承自 ::std::ostream,然後一堆程式都不必寫了。
結果今天在用 Statement 的時候,要把一個 time_t 的值插入 SQL 裡,因為應該也必須,我有在程式一開始的時候設定好 locale 為正體中文,結果要插入的 1423912421 就變成了 "1,423,912,421",多了三個逗號,產生了錯誤的 SQL 指令。很明顯地,因為偷懶繼承 ::std::ostream 的關係,現在必須承受所帶來的 side effect 的苦果。
更糟糕的是,C++ 的 locale/iostream,大概是除了 STL 之外,另外一個 compiler vendor 很容易沒有做到 standard compliant 的地方。我嘗試在 Statement 的 constructor 裡,使用 imbue 強制設定成使用 "C" locale,沒想到此舉竟然把所有的 iostream instances 全部都改回 "C" locale,使得我其他所有依賴正確 locale 設定的程式全部出了錯。
目前我暫時把那個 time_t 先用 snprintf() 得出字串後再用 << 插入 Statement 裡。但這當然不是好辦法。最終的解法,應該是改寫 Statement,使之維持 cout-like 的介面,但內部改用其他方式來實作。如果使用 ::std::ostringstream 就可以避開掉 side effect 的問題的話最好,否則的話,只好自己用 string 慢慢刻了。
C++98 Standard 27.1.1 如是說:
No function described in clause 27 except for ios_base::imbue causes any instances of basic_ios::imbue or basic_streambuf::imbue to be called. If any user function called from a function declared in clause 27 or as an overriding virtual function of any class declared in clause 27 calls imbue, the behavior is undefined.
所以使用 imbue 強制使用 "C" locale 不是個正確的方法,會造成 undefined behavior。如果強制使用 "C" locale 的想法是正確的,只是使用 imbue 是不對的手段的話,那我還有機會解決這個問題。
Post a Comment