C++的流設計很糟糕
阿新 • • 發佈:2018-12-27
最近需要提供一個功能,採用類似C++流輸出的格式輸出一些日誌資訊, 例如Log(FATAL) << "log to" .
我找了兩個類似專案來研究,google的glog 和 log4cpp, 它們都支援以C++流輸出格式進行輸出.
但是研究到最後,我發現最大的問題是, 如果按照C++的流輸出格式進行輸出, 將無法判定需要輸出的資訊到哪裡是結束.比如log << "hello " << "world",是無法判斷到底在輸出"hello"還是"world"的時候上面的引數輸入已經結束了.上面兩個專案中, 解決這個問題的辦法大致是相同的,以下面可編譯執行程式碼為例說明它們的做法(在linux g++下面編譯通過):
#include <iostream>
#include <sstream >
#ifdef __DEPRECATED
// Make GCC quiet. # undef __DEPRECATED
# include <strstream>
# define __DEPRECATED
#else
# include <strstream>#endifusingnamespace std;
class LoggerStream : public std::ostrstream {
public:
LoggerStream(char* buf, int len)
: ostrstream(buf, len),
buf_(buf),
len_(len) {
}
~LoggerStream() {
// do the real fucking output cout << buf_;
}
private:
char*buf_;
int len_;
};
int main() {
char buf[100] = {'\0'};
LoggerStream(buf, sizeof(buf)) <<1<<" hello world\n";
cout <<"buf = "<< buf << endl;
return0;
}
在上面的程式碼中, 開始進行輸出的時候首先初始化一個LoggerStream物件, 而在輸出引數輸入完畢的時候將呼叫它的解構函式,在這個解構函式中才完成真正的輸出動作.也就是說,由於對輸入引數結束位置判斷手段的缺失,C++中不得不採用這個手段在解構函式中完成最終的輸出工作.
這樣的做法,最大的問題是,頻繁的構造/析構開銷大,而且每個"<<"操作符背後又需要呼叫ostream的operator<<,也就是假如你的輸入引數有三個將呼叫operator <<三次(當然是經過過載的,不一定都是同一個operator<<),因此,假如需要考慮多執行緒的話,那麼一次輸入有多個函式函式中被呼叫,仍然是問題.天,要使用這門語言寫出正確的程式來,需要了解底下多少的細節呢?!
最後,我向專案組反映這個問題,一致同意以C中類似sprintf可變引數的形式實現這個功能.可變引數解決這個問題,就我的感覺而言,就是輸入引數的時候,稍顯複雜,需要使用者指定輸入的格式.然而,其實這個做法也有好處:作為函式的使用者,你必須明確的知道你在做什麼並且反饋給你所使用的函式.明確的,無歧義的使用函式,而不是依靠所謂函式過載猜你的用意,我想也是避免問題的一個手段.gcc中, 提供了對可變引數檢查的機制,見 這裡.
我找了兩個類似專案來研究,google的glog 和 log4cpp, 它們都支援以C++流輸出格式進行輸出.
但是研究到最後,我發現最大的問題是, 如果按照C++的流輸出格式進行輸出, 將無法判定需要輸出的資訊到哪裡是結束.比如log << "hello " << "world",是無法判斷到底在輸出"hello"還是"world"的時候上面的引數輸入已經結束了.上面兩個專案中, 解決這個問題的辦法大致是相同的,以下面可編譯執行程式碼為例說明它們的做法(在linux g++下面編譯通過):
#include <iostream>
#include <sstream
#ifdef __DEPRECATED
// Make GCC quiet. # undef __DEPRECATED
# include <strstream>
# define __DEPRECATED
#else
# include <strstream>#endifusingnamespace std;
class LoggerStream : public std::ostrstream {
public:
LoggerStream(char* buf, int len)
: ostrstream(buf, len),
buf_(buf),
len_(len) {
}
// do the real fucking output cout << buf_;
}
private:
char*buf_;
int len_;
};
int main() {
char buf[100] = {'\0'};
LoggerStream(buf, sizeof(buf)) <<1<<" hello world\n";
cout <<"buf = "<< buf << endl;
return0;
}
在上面的程式碼中, 開始進行輸出的時候首先初始化一個LoggerStream物件, 而在輸出引數輸入完畢的時候將呼叫它的解構函式,在這個解構函式中才完成真正的輸出動作.也就是說,由於對輸入引數結束位置判斷手段的缺失,C++中不得不採用這個手段在解構函式中完成最終的輸出工作.
這樣的做法,最大的問題是,頻繁的構造/析構開銷大,而且每個"<<"操作符背後又需要呼叫ostream的operator<<,也就是假如你的輸入引數有三個將呼叫operator <<三次(當然是經過過載的,不一定都是同一個operator<<),因此,假如需要考慮多執行緒的話,那麼一次輸入有多個函式函式中被呼叫,仍然是問題.天,要使用這門語言寫出正確的程式來,需要了解底下多少的細節呢?!
最後,我向專案組反映這個問題,一致同意以C中類似sprintf可變引數的形式實現這個功能.可變引數解決這個問題,就我的感覺而言,就是輸入引數的時候,稍顯複雜,需要使用者指定輸入的格式.然而,其實這個做法也有好處:作為函式的使用者,你必須明確的知道你在做什麼並且反饋給你所使用的函式.明確的,無歧義的使用函式,而不是依靠所謂函式過載猜你的用意,我想也是避免問題的一個手段.gcc中, 提供了對可變引數檢查的機制,見