• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            那誰的技術博客

            感興趣領域:高性能服務器編程,存儲,算法,Linux內核
            隨筆 - 210, 文章 - 0, 評論 - 1183, 引用 - 0
            數據加載中……

            C++的流設計很糟糕

            最近需要提供一個功能,采用類似C++流輸出的格式輸出一些日志信息, 例如Log(FATAL) << "log to" .

            我找了兩個類似項目來研究,google的gloglog4cpp, 它們都支持以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>
            #endif

            using namespace 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;

              
            return 0;
            }

            在上面的代碼中, 開始進行輸出的時候首先初始化一個LoggerStream對象, 而在輸出參數輸入完畢的時候將調用它的析構函數,在這個析構函數中才完成真正的輸出動作.也就是說,由于對輸入參數結束位置判斷手段的缺失,C++中不得不采用這個手段在析構函數中完成最終的輸出工作.
            這樣的做法,最大的問題是,頻繁的構造/析構開銷大,而且每個"<<"操作符背后又需要調用ostream的operator<<,也就是假如你的輸入參數有三個將調用operator <<三次(當然是經過重載的,不一定都是同一個operator<<),因此,假如需要考慮多線程的話,那么一次輸入有多個函數函數中被調用,仍然是問題.天,要使用這門語言寫出正確的程序來,需要了解底下多少的細節呢?!

            最后,我向項目組反映這個問題,一致同意以C中類似sprintf可變參數的形式實現這個功能.可變參數解決這個問題,就我的感覺而言,就是輸入參數的時候,稍顯復雜,需要用戶指定輸入的格式.然而,其實這個做法也有好處:作為函數的使用者,你必須明確的知道你在做什么并且反饋給你所使用的函數.明確的,無歧義的使用函數,而不是依靠所謂函數重載猜你的用意,我想也是避免問題的一個手段.gcc中, 提供了對可變參數檢查的機制,見這里.

            posted on 2010-07-06 13:04 那誰 閱讀(14788) 評論(79)  編輯 收藏 引用 所屬分類: C\C++

            評論

            # re: C++的流設計很糟糕  回復  更多評論   

            你為什么要了解細節呢,你只需要知道無論什么類型只要編譯通過都能operator<<就行了。而且結束很好判斷,自己寫一個全局變量:

            class MyEndLine
            {
            } myEndLine;

            重載operator<<(MyStream&, const MyEndLine&)

            最后
            myStream<<1<<2<<3<<MyEndLine。

            解決。

            可變參數什么的,沒有討論的余地,新代碼絕對不能用。除非你確信你、你的同事或者你的手下的水平都高超到用可變參數而絕對不會產生內存問題譬如說溢出啊之類。
            2010-07-06 13:52 | 陳梓瀚(vczh)

            # re: C++的流設計很糟糕  回復  更多評論   

            樓主,批C++可以,但你為什么要這樣批呢?

            # re: C++的流設計很糟糕  回復  更多評論   

            使用C++類庫的大忌之一:通過閱讀代碼而不是寫測試來了解功能
            使用C++類庫的大忌之二:如果有文檔/demo,也非得通過閱讀代碼而不是寫測試來了解功能
            使用C++類庫的大忌之三:通過閱讀代碼而不是寫測試來獲得性能上的數據

            總之:非必要情況下,如果你非得了解代碼才能知道一個類庫怎么用,而且如果類庫的文檔/demo還很好的話,那絕對是你自己的問題。

            一般來說,質量好的C++的類庫都做到,編譯通過了一般都不會有【大】問題,就算有了問題也會被assert出來的。
            2010-07-06 13:54 | 陳梓瀚(vczh)

            # re: C++的流設計很糟糕  回復  更多評論   

            @陳梓瀚(vczh)
            嗯,你說的那種方式 確實也是個辦法吧.
            2010-07-06 13:55 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            所以為了項目的安全,我還是建議你不要使用可變參數
            2010-07-06 13:56 | 陳梓瀚(vczh)

            # re: C++的流設計很糟糕  回復  更多評論   

            @陳梓瀚(vczh)
            C++的設計里面 編譯器做了太多額外的事情 以至于你要用好這么語言不得不去多了解細節 我覺得這是很糟糕的地方 因為你需要付出很大的代價才能對這門語言有足夠的了解.
            2010-07-06 14:03 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            @陳梓瀚(vczh)
            我想了想 你這樣還是有問題的,比如一次輸入幾個參數 將會在幾次函數調用中完成 如果我需要做到是多線程的 這一點如何保證呢?還是類sprintf那樣的在一個函數中搞定所有的事情吧.
            2010-07-06 14:11 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            鮮明的對比:

            學會了errno就學不會exception。
            學會了prints就學不會streams。
            學會了C就學不會C++。
            。。。

            學會了errno繼續學exception。
            學會了printfs繼續學streams。
            學會了C繼續學C++。
            。。。
            2010-07-06 14:16 | OwnWaterloo

            # re: C++的流設計很糟糕  回復  更多評論   

            #define LOG_PUTS(log, record) \
            do{ \
            std::ostringstream os; \
            os << record; \
            lg->puts(os.str().c_str(), os.str().length()); \
            }while(0)


            這樣不就行了嗎
            2010-07-06 14:22 | cui

            # re: C++的流設計很糟糕  回復  更多評論   

            既然是寫log,你不會想要調用一次operator <<就真的寫一次設備吧?那可是IO速度差著幾個數量級的設備呢(C++的標準庫也是用了buffer,不信你可以去測)

            否則這樣的操作效率何在?xxx << 1 << 2 << 3 << 4 << 5 << 6 << 7;

            熟悉C的話,可以這樣理解:一次<<其實就是添加一個sprintf中的參數

            建議在碰到endl再真的向物理設備輸出。這樣的話thread safe很容易做到——如果不這樣的話,多線程一起輸出log,即使thread safe了,log也沒法看了

            # re: C++的流設計很糟糕[未登錄]  回復  更多評論   

            兄弟, 你走遠了吧. log4cpp的流使用方式就很好啊,類似如此:

            #define SLOG(Level,Event) \
            do{ \
            std::ostringstream _SLOG_BUF_INTERNAL_; \
            _SLOG_BUF_INTERNAL_<<Event; \
            printf_log(Level,_SLOG_BUF_INTERNAL_.str().c_str()); \
            }while(0)
            2010-07-06 14:28 | cppexplore

            # re: C++的流設計很糟糕[未登錄]  回復  更多評論   

            @cui
            ......................驚人的一致............
            2010-07-06 14:29 | cppexplore

            # re: C++的流設計很糟糕  回復  更多評論   

            我寫過一個小的日志庫,已經在產品中使用過,
            http://code.google.com/p/cute-log/
            2010-07-06 14:55 | cui

            # re: C++的流設計很糟糕  回復  更多評論   

            最大的問題是,頻繁的構造/析構開銷大
            這個應該可以通過單例模式解決吧
            你這篇文章是不是主要想說明輸出多個參數的時候會多次調用“<<”,
            而使用“sprintf”效率會比較高,是這樣么?
            2010-07-06 15:00 | joewan

            # re: C++的流設計很糟糕  回復  更多評論   

            是LoggerStream寫得有問題,如果你要這樣寫,可以考慮寫一個類似endl的操作子來做真正的輸出
            2010-07-06 16:25 | t

            # re: C++的流設計很糟糕  回復  更多評論   

            記得上次看你一篇博文,google的c++編程規范里面規定了不準使用c++的流,全部用printf一族。對于蹩腳的東西干脆不用,很好。
            2010-07-06 17:03 | 趙建寅

            # re: C++的流設計很糟糕  回復  更多評論   

            linux kernel,apache,lighttpd,nginx,mysql,postgresql都是純C寫的(mysql中有少量C++代碼,在非核心的部分)。C++程序員應該反思。C++的某些語言特性在誘導程序員作出不合理的設計。。。KISS是王道。
            2010-07-06 21:00 | c-programmer

            # re: C++的流設計很糟糕  回復  更多評論   

            @陳梓瀚(vczh)
            另外,還有個問題,很難在編譯語法層面保證你的最后一個輸入是那個標記類吧....
            2010-07-06 22:26 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            to cui and cppexplore
            我們看的不是一個地方,呵呵.
            2010-07-06 22:35 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            C++ 流其實就是一個文件流,其實當初設計C++ 的人,大多數是混口飯吃的,當然,他們的責任心是有的,但是責任心跟專心是有區別的,愛好者很多時候,比專業人士對于該專業更加用心。
            2010-07-06 22:43 | 飛鴿傳書

            # re: C++的流設計很糟糕  回復  更多評論   

            能用C方式解決的就用C,該用STL的就用STL。
            我覺得如何發現蹩腳,那就是選擇的不對。或者是使用的不對。
            2010-07-06 22:52 | xx

            # re: C++的流設計很糟糕  回復  更多評論   

            @陳梓瀚(vczh)
            為什么說是大忌呢?
            2010-07-07 02:40 | 夜風

            # re: C++的流設計很糟糕  回復  更多評論   

            C++比較適合描述數據關系
            C比較適合描述流程
            應該結合起來用。

            不沖突。


            2010-07-07 03:46 | johndragon

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            多線程輸出IO信息是不對的,請修改設計。
            2010-07-07 11:14 | 陳梓瀚(vczh)

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            C++難學是難學,你見過有人抱怨過微積分太難學而說幾何比微積分好的嗎(初級的微積分解決的問題跟幾何是一樣的)
            2010-07-07 11:15 | 陳梓瀚(vczh)

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            實在不行,你可以
            Write(StringBuidler<<"vczh"<<" is "<<" a "<<" programmer!");總行了吧。雖然長了一點,也總比printf好。之余語法怎么調整你自己去想就好了。
            2010-07-07 11:18 | 陳梓瀚(vczh)

            # re: C++的流設計很糟糕  回復  更多評論   

            @陳梓瀚(vczh)
            呵呵,不知道你有沒有寫過服務器端程序,多線程同時寫log是肯定會存在的.
            至于說的sprintf有緩沖區溢出問題,也可以有做法進行避免.
            總之,我的結論是C++的流在判斷輸入結束方面存在缺陷,至于后面跟的帖子寫的其他格式,則不是我關注的重點了,我這篇文章只為了說明C++的這個缺陷,這是我寫這篇文章的目的.

            另外,你那個微積分和幾何的比喻放在這里不妥,兩者不能解決相同的問題,不屬于一個類型.
            2010-07-07 11:24 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            我覺得不是需不需要多線程log的問題,而是實際上在寫的線程只能有一個。舉個例子,你有一個log線程,暴露一個隊列,其他線程往里面填,然后log線程去寫文件。

            C++難并不是缺陷,其實每一個細節都規定的十分清晰。而且C++那些也不是毫無規律的,C++試圖做到每一個語法你都可以去寫callback,應該好好理解這一點。
            2010-07-07 11:44 | 陳梓瀚(vczh)

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            變量創建銷毀有callback(構造函數析構函數),復制有callback,操作符有callback(可以重載),等等。當然我認為構造函數和析構函數是非常簡單的,復雜的是overloading,一般來說我很少使用。實際上最復雜的是模板,這個你不反對吧,而且我認為你們也應該不會使用模板,或者僅僅使用模板的低級形式。因此拋開這些之后,我不知道你認為還有什么地方是“復雜”的。如果你說的是stl復雜,那顯然跟C++沒關系。
            2010-07-07 11:46 | 陳梓瀚(vczh)

            # re: C++的流設計很糟糕  回復  更多評論   

            @陳梓瀚(vczh)
            "其實每一個細節都規定的十分清晰"
            我還真沒有覺得,也許是真的,但是要做到使用者也非常清晰,代價很大.就學一門語言的代價而言,我覺得過大,因為語言不是全部,還有很多需要學的,如果過分多的把精力放在語言學習上,我覺得有點本末倒置.所以,我現在只使用那些我清楚的,有把握的C++特性,你可以說我保守,但是我不是學生,沒多少時間花在語言學習上.
            2010-07-07 11:47 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            @陳梓瀚(vczh)
            也有用模板,用模板創建callback,但是我不了解做法,只了解怎么使用,模板讓我很崩潰,一直不想深究.
            2010-07-07 11:48 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            我開了個新的首頁的文章,我們挪去那里好了,你可以把你認為受不了的地方列出來,然后我們討論討論。當然最好說的詳細一點,這樣有助于雙方的理解。
            2010-07-07 11:54 | 陳梓瀚(vczh)

            # re: C++的流設計很糟糕  回復  更多評論   

            C++語言本身沒有任何問題,是你掌握得不是很熟練。
            C++作為語言,只是一種表達工具,而一個設計的好壞跟語言本身是沒有任何關系的,只要有思想,C++可以做出非常好的設計的。
            之所以以懷疑流的使用,只是你還沒有理解流,還沒有了解如何學得更好。
            別動不動就批C++,還沒有理解它怎么能知道它不好呢?
            C++難是難,學起來非常困難,難是因為它太強大,造自行車不難,學會騎自行車也不難,而造飛機與開飛機就非常難,沒開好飛機摔了,能說飛機不好嗎?
            2010-07-08 13:06 | Noock

            # re: C++的流設計很糟糕  回復  更多評論   

            @Noock
            那請您就事論事說一說怎么解決我提的問題,謝謝.
            2010-07-08 13:08 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            說C++復雜,那是因為你沒清楚如何使用她~

            C++標準庫的IO是挺不錯的架設,其實,你或許不應該從ostrstream繼承,應該從basic_ostream,然后設計自己的basic_streambuf ,相當簡單實用
            2010-07-08 18:04 | 陳煜

            # re: C++的流設計很糟糕  回復  更多評論   

            @陳煜
            那請您給出可運行的代碼例子并且解決我上面的問題,謝謝.


            2010-07-08 18:11 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            請看
            http://www.drdobbs.com/184401470;jsessionid=3I5YVJBUOELIJQE1GHPSKHWATMY32JVN?pgno=1

            還有
            http://www.codeproject.com/KB/debug/debugout.aspx
            2010-07-08 21:39 | 陳煜

            # re: C++的流設計很糟糕  回復  更多評論   

            @陳煜
            呵呵,我把那個codeproject的代碼拉下來編譯驗證,正是我上面給出來的結論.麻煩你自己回頭看看那份代碼和我文章中的描述吧.
            以那個項目的代碼為例,在類basic_debugbuf的析構中調用了sync,這個函數中再調用output_debug_string輸出字符.就是我文章中提到的情況:因為C++的流輸出對輸入參數的結束位置無法判斷,只能在析構函數中做真正的輸出.

            另外那篇文章,太長了,我不去看了.
            2010-07-08 21:55 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            為啥不使用flush呢?
            2010-07-08 22:01 | 陳煜

            # re: C++的流設計很糟糕  回復  更多評論   

            @陳煜
            行了,我證明了你說的辦法不能解決我這里提的問題.就這樣吧.
            給他人下結論之前,麻煩你做過充分的驗證,我在上面可是有給出可編譯運行的程序的,謝謝.
            2010-07-08 22:04 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            @陳煜
            另外,這個問題跟flush沒有一毛錢的關系,你這么問說明你對我提出的問題還是不了解,呵呵.
            2010-07-08 22:36 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            #include <iostream>
            #include <ostream>
            #include <sstream>
            #include <strstream>

            using namespace std;

            struct streamend { };

            class LoggerStream : public stringstream //public std::ostrstream
            {
            public:
            static streamend end;
            ~LoggerStream( ) { DoPrint(); }

            private:
            void DoPrint( void )
            {
            cout<<"------- DoPrint( ) ------------"<<endl; // 只是用來證明何時調用了這個東東
            cout<<this->str(); // real string
            this->str("");
            }
            private:
            friend ostream& operator<<(ostream& os, const streamend& end);
            };

            streamend LoggerStream::end;

            // 第二個參數只是用來支持結束符
            ostream& operator<<(ostream& os, const streamend&)
            {
            #if 1 // 0或1兩種方法都可以
            ostream* pos = &os;
            LoggerStream* pls = dynamic_cast<LoggerStream*>(pos);
            if( pls != NULL)
            pls->DoPrint();
            #else
            if( typeid(os) == typeid(LoggerStream))
            ((LoggerStream*)(&os))->DoPrint();
            #endif
            return os;
            }

            int main()
            {
            LoggerStream lstream; // 你只需要構造一次,也可以聲明為靜態的
            lstream << 1 << " hello world\n"<<LoggerStream::end;
            lstream <<"line 1"<<LoggerStream::end<<"\nline 2\n"<<LoggerStream::end;

            stringstream ss;
            ss<<"hello"<<LoggerStream::end; // 此處LoggerStream::end沒有任何影響
            return 0;
            }
            2010-07-08 23:09 | Noock

            # re: C++的流設計很糟糕  回復  更多評論   

            @Noock
            你這個辦法上面已經有人說過了.請看我的回復,謝謝.
            2010-07-08 23:14 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            不解決你說的問題嗎?
            2010-07-08 23:17 | Noock

            # re: C++的流設計很糟糕  回復  更多評論   

            @Noock
            請問你如何從語法,編譯器的角度避免用戶沒有輸入最后那個end呢?
            2010-07-08 23:19 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            我在上面已經就這個方式進行了回復,恕我不再回復.
            再說一句,給他人定性下結論之前,自己先看清楚問題,和別人的回復,同時自己去驗證過可行性,謝謝.
            2010-07-08 23:21 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            另外,還有個問題,很難在編譯語法層面保證你的最后一個輸入是那個標記類吧....


            這個你使用printf更容易出問題,后面幾個參數在編譯時是檢測不出來的,運行時會致命的。
            而且使用printf %d 輸出long long都有可能致命

            而且,你定義的變量日后修改類弄,你容易沒的修改格式字符串,那樣你的程序會在不必要的寫日志時崩潰,而且是不確定的崩潰,在不確定平臺崩潰,有的平臺會死,有些平臺卻沒事兒
            2010-07-08 23:25 | Noock

            # re: C++的流設計很糟糕  回復  更多評論   

            @Noock
            sprintf可以使用編譯器的特性進行檢查,gcc就可以做到.
            2010-07-08 23:27 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            編譯器擴展是犧牲了可移植性的,當然你也可以通過寫個宏去避免這個問題


            #define LOG( content ) lstream<<content<<LoggerStream::end
            LOG( "str1"<<1<<"str"<<2);

            這個能不能滿足你的要求呢?
            2010-07-08 23:41 | Noock

            # re: C++的流設計很糟糕  回復  更多評論   

            @Noock
            行了,到此打住吧,我只想證明這個東西是確實有缺陷的.到此為止.
            2010-07-08 23:41 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            至少現在C++通過自己的特性還是解決了你的問題,運算符只是個函數而已,如何去使用是程序員自己的事,C++委員會也不會接受為了一個函數調用去修改C++的特性的。這個與C++的流設計也沒有什么關系,你上來就一個大標題,

            “C++的流設計很糟糕”

            是不是有點兒太不負責任了?
            2010-07-08 23:47 | Noock

            # re: C++的流設計很糟糕  回復  更多評論   

            @Noock
            我上面已經回復了,你那個辦法怎么叫解決?不是又引入了新的問題么?
            "請問你如何從語法,編譯器的角度避免用戶沒有輸入最后那個end呢?"

            你的另一種做法,不是解決,叫規避,謝謝.
            2010-07-09 00:45 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            說白了,你提供了這個機制,又不提供相應的檢查機制,如何叫"解決"?
            2010-07-09 00:47 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            別老想著讓編譯器解決,C++設計語言的指導思想之一就是能通過已有特性解決的問題就不進行語言特性擴展,別一門心思地讓編譯器來解決
            編譯器這么多,只要不列入C++標準,所有的擴展都是不可移植的
            考慮問題是不是可以換個角度呢?
            2010-07-09 09:27 | Noock

            # re: C++的流設計很糟糕  回復  更多評論   

            @Noock
            "解決"問題應該是"自封閉"的,也就是不引入別的問題.在這里我提出的類sprintf的解決方式,帶來的格式輸入有誤,緩沖區溢出等問題,我都有方法解決掉,這才叫"解決"問題.
            你的第一種方式,帶來的另外一個問題,你沒有幫我"解決"掉,所以,你這不叫"解決"問題.
            斗膽說一句,平時工作中,你都是這么給人"解決"問題的么?假設你是制造車的,我要解決代步問題,從你那里買輛車,如果還要擔心剎車會失靈,這個能叫做"解決"問題么?

            你的另一種方式,不是"解決"問題,相反,恰恰如我說的那樣,是這種方式存在缺陷,你才要使用別的方式規避它,這也就反證了這個方式是存在缺陷的了.

            你說到編譯器不能解決所有的問題,我承認,但是要最大限度讓編譯器發揮作用來幫助解決問題,人的因素很不穩定,不能把項目的成敗過多的放在這些不穩定因素中.

            2010-07-09 10:34 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            對C++流的抱怨也不是一天兩天了,老是使用C的sprintf也不能算好的辦法,最好能使用類型安全的printf,boost::format是一種解決辦法,還有一個fastformat也可以看一看,但是完美的設計是不存在的,還是看自己的需求吧。
            2010-07-09 11:18 | wuqq

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            軟件最基本要求是正確,請教如果使用printf下面這個問題如何解決?
            char cc = -1;
            printf("cc=%u\n", cc);
            gcc 4.3.4 :
            cc=4294967295

            g++ -Wall 沒有任何警告
            2010-07-09 13:38 | Noock

            # re: C++的流設計很糟糕  回復  更多評論   

            @Noock
            請看這里:
            http://blog.chinaunix.net/u3/91522/showart_2054004.html
            我的做法會在項目組內禁止直接使用printf,而使用加上了__attribute__封裝的函數.
            緊跟著的問題是,如何能保證禁止直接使用printf呢,define宏解決.
            2010-07-09 13:47 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            1) gcc標準庫的printf也是加了__attribute__選項的,也能解決大部分的格式符,但上面我的那個例子是不行的,
            你可以試一下你自己寫的函數

            你也可以把你的函數帖出來,我幫你驗證一下。

            2)既然你使用自己封裝的函數來打印,跟我原來定義一個宏LOG( )當函數用來比有什么優勢。
            2010-07-09 15:14 | Noock

            # re: C++的流設計很糟糕  回復  更多評論   

            @Noock
            是的,我測試了一下,確實不行.查了一些文檔,發現是因為C語言里面認為,以%u輸出char是正確的,比如這里:
            http://blog.csdn.net/wangyadong/archive/2009/05/22/4208013.aspx

            我把代碼貼在這里:

            #include <stdarg.h>

            extern void my_printf(const char *format,...) __attribute__((format(printf,1,2)));

            #define printf my_printf

            int main() {
            char cc = -1;
            printf("cc=%u\n", cc);

            return 0;
            }

            void my_printf(const char *format,...) {
            // do the really fuck output
            }

            使用宏替代掉系統的printf的作用是,用戶可以完全不知道后面的改動,照常使用printf的功能.而你的LOG宏,只是規避了問題,沒有解決我提出的log<<"hello"<<"world"無法判斷結束符的問題,如果你有一種辦法,可以不改變我用戶的輸入,而解決這個問題并且不帶來新的問題,這個才算是解決吧.
            2010-07-09 18:52 | 那誰

            # re: C++的流設計很糟糕  回復  更多評論   

            @那誰
            不光是對于char型的不行,對于int、short使用%u輸出也不行,這是printf最致命的問題。

            你可以不承認LOG宏是解決了你的問題,但它是一個可以獲得期望行為的,保證輸出的正確性,而使用printf會導致程序錯誤的輸出。

            如果你非得要使用printf,給個建議,在自定義的函數內檢查格式字符串,當發現有%u時,可以檢測它的值是正的還是負的,如果是負的可以給個警告或者拋出異常,保證行為的正確性。

            不要在“規避”還是“解決”兩個詞之間找別扭了,保證程序的正確性才是根本。
            printf會給你提供更多的出錯機會
            還要程序員時刻得著各個轉義字符的對應關系,我是比較懶,我是記不住
            而且__attribute__也只有GCC在新版本中擴展功能才有,其它編譯器是不支持的
            使用printf還會影響到使用std::string,這兩個混著用還經常會忘了調用.c_str()方法,這樣在有些編譯器是安全的,但很多編譯器也會產生致命的錯誤
            使用printf如果參數比較多的話還容易造成順序錯誤
            2010-07-09 20:41 | Noock

            # re: C++的流設計很糟糕  回復  更多評論   

            @Noock
            我文章的目的,是要說明C++的這種機制存在缺陷,已經強調了很多次.你可以"規避"這個問題,不能否認我的結論.
            2010-07-09 20:50 | 那誰

            # re: C++的流設計很糟糕[未登錄]  回復  更多評論   

            沒太看明白。lz的意思是不是說C++的流設計倒是用戶不能確定什么時候寫入的信息會真正流入到IO設備里,如果是這樣的話我覺得這種東西應該放在文檔里。

            stream << "abc" << "def" << endl; 這里你看不出來什么時候結束,
            難道printf("%s%s\r\n", "abc", "def");你就能看出來了? 還不是要看文檔才知道?
            2010-07-09 23:15 | 欲三更

            # re: C++的流設計很糟糕  回復  更多評論   

            服務器寫log的,通常量不會太大,每次構造一個對象也不會成為瓶頸吧;

            我是這樣解決的,既可以保證類型安全,也可以保證線程安全;
            當然如果你不用syslog自己寫一個線程安全的flush函數也沒問題啊

            struct Log
            {
            int _lv;
            std::stringstream _ss;

            ~Log()
            {
            syslog("%s\n", ss.str().data());
            }

            template<typename T1, typename T2, ... typename Tn>
            Log(int lv, const T1 &t1, const T2 &t2, ... typename Tn)
            : _lv(lv)
            {
            _ss << t1 << t2 << ... tn;
            }
            }

            int main()
            {
            Log(info, "a=", 1, "; b=", 2);
            }
            2010-07-10 16:55 | 路過

            # re: C++的流設計很糟糕  回復  更多評論   

            #define LOG(x) do{_log<<x<<endl;}while(0)

            LOG(a<<b<<c<<d<<e);

            這個呢,沒有了<<endl的問題,編譯器也能檢查類型。
            2010-07-12 20:30 | 陳梓瀚(vczh)

            # re: C++的流設計很糟糕  回復  更多評論   

            雖然我不是很懂C++,但是我還是懂邏輯的。
            后面幾個回復已經答復了你的問題。
            C++的機制是最自然合理的處理方式。
            其實出現問題的原因是:實例里面的logstream并沒有按照C++ io的套路來,
            沒有加上一個結束的標示: endl.
            2010-07-28 22:20 | halida

            # re: C++的流設計很糟糕  回復  更多評論   

            感覺樓主對C++語言還缺乏較為深入的理解,下面對幾個問題做點說明,其實很簡單,很多人不懂,是因為C++標準教材沒這些東西。C++是一門在工業實踐中成長起來的語言,工業界發明這些東西是因為需要,學院派卻總跟不上進度,教材幾十年一變。要用C++,就要做好準備,否則,你干嘛不用Java或者C#。

            1. 關于所謂“頻繁的構造/析構開銷大”
            你首先要清楚“構造”和“析構”中編譯器到底為你做了什么。1.)分配對象空間:如果是在堆中分配對象,那么會有一個代價很大的堆分配(new,在2.7G的CPU上單線程new性能是5M次/秒);如果在堆棧上分配,內存分配代價幾乎為零。2)調用構造函數和析構函數,這有兩個開銷,一個是調用本身的開銷,一個是函數體內部代碼的開銷,很明顯,前者才C++帶來的額外開銷。我可以告訴你的是,如果是內聯,這個開銷為0,如果不是內聯,這個開銷在2.7G的CPU上單線程性能是1200M次/秒,作為類比,2.7G的CPU上單線程可以做400M次32位整型變量寫入操作,也就是這個開銷比寫一個整型變量還小。
            現在,看看你說的情況,局部對象的構造和析構,每次的代價比寫一個32位整型的變量還小得多,相比每次日志輸出至少十幾個字節的內存拷貝,這點開銷完全可以忽略不計,除非打算每秒中打算做1M次的日志,它帶來的代價不占用1%的CPU而已,不過事實是,每秒鐘寫不了1M次的文件IO。
            最后從設計的角度考慮這個問題,你的系統打算每秒中寫多少次日志,應該心理有數吧,從這個意義上,從設計的角度,上面我寫的那些分析毫無必要,只是為了加深對C++的理解,事實是,即便“頻繁的構造/析構開銷大”很大,它們仍然不是系統的真正瓶頸,沒必要過早優化。如果它們真成了瓶頸,你應該做的事情是,調整成合理的日志策略。





            2010-08-03 22:20 | maxime

            # re: C++的流設計很糟糕  回復  更多評論   

            2.所謂“比如log << "hello " << "world",是無法判斷到底在輸出"hello"還是"world"的時候上面的參數輸入已經結束了”

            其實,這個問題,流的設計者早已考慮到了,std::endl就是用來干這件事情的。事實上,自定義的流操控符,還可以干很多事情比如:
            std::cout << v1 << mylock(v2) << v2 << myunlock(v2);
            上面的mylock,myunlock就是自定義的操作符,用來給v2加鎖解鎖,而不輸出任何字符。它到底能做什么,取決于你的想象力。我總愛把C++比作機械行業的鉗工,他們比不上機器的速度,但沒他們不行,很多事情機器做不了。使用正確的工具做正確的事情,如果你感覺不對,先想想選對工具沒,而不是抱怨工具很爛。

            額外,說明一點,有人告訴你sprintf存在寫錯的可能性,所以,你可以說,如果別人忘了寫上他的endl怎么辦?

            我來告訴你吧,寫錯了其實沒什么大不了的,問題關鍵是,寫錯了會帶來什么危害。sprintf寫錯了,可能帶來的是內存溢出覆蓋,這才是我們恐懼他的原因,一個內存溢出帶來的危害我就不說了。
            反之,少寫了一個endl,最多就是兩行日志重疊,或者一個日志輸出時間晚了一會兒。如果你真看到這個情況,把endl加上去就行了。

            不知道現在是否能理解了,不要害怕bug,不要害怕寫錯,要怕會讓你掉進深淵的bug。我得承認,這是C/C++的弱點,java/C#相對好很多。
            C++最害怕的,就是指針操作,內存覆蓋可以毀掉整個程序的運行基礎,卻不容易找到錯誤的代碼。但這也是C++的優點,C++為什么要用流替換C的sprintf,就是要減少內存覆蓋錯誤的機會。當然,C++中仍然有這種錯誤的機會,因為拋棄了指針,C++和Java就沒區別了。如果說C是做操作系統的,java是做應用的,C++就是做系統和應用結合部的,只有理解了這點,你才能用好C++,而不是抱怨,它既沒C簡單,也沒java安全。
            事實是,C++就是這么個怪胎,比Java更快,比C更安全更有開發效率。



















            2010-08-03 22:38 | maxime

            # re: C++的流設計很糟糕  回復  更多評論   

            3. 關于“要使用這門語言寫出正確的程序來,需要了解底下多少的細節呢?!”

            首先答案是,不需要知道細節,只需要知道“規范”。C++真正的問題不是太復雜,而是在實踐中缺乏規范,尤其在中國的軟件作坊里面。就像你會開汽車一樣的,你沒比要知道汽車發動機原理,同樣能把汽車開好。因為你遵守了開汽車的規范,比如啟動的時候,慢加油門。

            很多人的問題在于,在思想上,忽視了規范,到頭來卻怪東西太復雜。

            其次是了解細節,可以工作更深入。再說了,就算復雜,C++能有多復雜,一個C++語言里面能有多少東西呢?相比一個Java庫,這點東西真算不了什么。很多人掌握不好,是因為沒有正正經經的機會去學,去練。這點像數學,學的時候比較枯燥,不管怎么說,這點東西就叫復雜,那只能說,做的應用系統太簡單。
            2010-08-03 22:50 | maxime

            # re: C++的流設計很糟糕  回復  更多評論   

            4. 關于“假如需要考慮多線程的話,那么一次輸入有多個函數函數中被調用”

            要在多線程進行IO操作,肯定是要用鎖的,就算你不直接用,系統API的流API,比如Win32的WriteFile,也是要用的。


            所以,答案很簡單,用鎖。問題不在于有幾次函數調用,而在于能否讓這幾次函數調用位于同一個鎖當中。

            傳統上,一個sprinf,你可以加一次鎖,就夠了。
            而現在呢,分成了好幾次調用,那么就在這幾次調用之間和之后加鎖就行了,在本例中,也就是那個被認為過于調用繁瑣的臨時對象了,在它的構造函數加鎖,在它的析構函數中解鎖,就能保證輸出的原子性。如果這樣還不滿意,還可以考慮流操控符加鎖,不過有點危險。


            不過呢,說道最后,如果你明白,那個看似效率低下的臨時對象其實對整行的輸出做了緩存,所以在glog中,臨時對象中是沒必要用鎖的,因為臨時對象中保存的字串是不會被多線程打斷的,它能夠保證所有的“<<”調用在輸出上的原子性。最后析構函數中,真正進行輸出時,在下層的實際輸出位置,實際上是有鎖。



            2010-08-04 00:03 | maxime

            # re: C++的流設計很糟糕  回復  更多評論   

            5. 最后談一下,C++流的真正缺點?

            從安全性的角度講,C++流相對sprintf是一次飛躍。從實際項目來看,C++程序員的代碼產出和維護量,通常會數倍甚至幾十倍于C程序員,這表面了在某些問題域上,C++比更有開發效率。

            但由此帶來的問題是,在代碼量少的時候,C程序員可以花時間慢慢檢查代碼,保證sprintf沒問題。而C++程序員再這樣做效率就太低了。所以才會有了C++流的方案,C++流設計者正是從實踐中品嘗到了sprintf的苦果。

            事實是,C++語法形式,從實用性角度,的確很蹩腳。而且性能只有sprintf的1/3.不過實際環境下,性能通常不是問題,流輸出很少會是一個應用系統真正的瓶頸。

            蹩腳的語法,是個問題,尤其當你需要做格式控制的時候,代碼可能非常長。這個問題,我的看法是,寫的時候可能多花點時間,不過以后維護起來就輕松了。畢竟,我寧愿選擇安全性,花三天時間去找一個緩沖區溢出是不會寧人愉悅的。當你認為語法問題很重要時,通常暗示代碼管理上有問題。我通常認為代碼的書寫只占20%的時間,80%時間是在維護代碼。維護效率遠比書寫效率重要。

            在C++領域,新發明似乎是沒有止境的,有一個新的,利用重載“()”操作符的格式化庫出現了,具體我本人沒有用過,看起來還不錯,據說在性能上優于sprintf,在安全性上不輸于C++流,在格式上類似sprintf。由于缺乏大規模應用,實際情況如何,還不好說。


            就我本人而言,我認為C++流的效率和格式問題,并非致命問題,所以也就不急著使用更先進的東西了,短期內我C++流仍是最好的格式化輸出工具。除非,項目主要業務邏輯就是格式化字符串,那也許我會選擇sprintf或者其他的東西。















            2010-08-04 00:22 | maxime

            # re: C++的流設計很糟糕  回復  更多評論   

            最后,我感覺樓主,似乎想在一個輸出語句中,輸出很長很長的,可能跨越多次物理輸出的內容。

            這樣做,首先代碼不易理解,不易修改維護。

            根據本人的實際經驗來看,日志輸出最好還是按實際物理行為單位比較好,所以glog沒有支持所謂endl特性。

            樓主可能真正擔心的是另一個問題,在多線程程環境下,想要連續輸出的幾行文本,會被其他線程打斷,以致閱讀性變差。

            對此,我建議,如果不希望被打斷,使用glog那就需要八幾行輸出寫在一個glog句子,作為一次原子輸出就行了。但是,如果樓主對這樣的原子輸出,還要求再被分成多次物理輸出,那這是為什么呢?有這個必要嗎?既然打算連續輸出幾行,且在一個語句之中,整個語句時間是非常快的,對觀察者而言,一次原子輸出是由一次物理輸出還是多次物理輸出構成,沒有任何實際意義。








            2010-08-04 00:36 | maxime

            # re: C++的流設計很糟糕  回復  更多評論   

            剛才服務器崩潰了,估計又是(我是用)printf的(問題)。譬如%s,結果我的參數為int型別。
            之前測試程序時,就出過這類錯誤。不過解決倒也容易。
            2010-08-26 21:29 | 哭啊

            # re: C++的流設計很糟糕  回復  更多評論   

            都是大神
            2010-10-29 14:55 | 刀刀

            # re: C++的流設計很糟糕  回復  更多評論   

            在CPP 博客批CPP
            哈哈 找抽

            懂c++的c太他們簡單的玩意
            2010-12-27 14:11 | 法國風格

            # re: C++的流設計很糟糕  回復  更多評論   

            Set your life time more simple take the <a href="http://bestfinance-blog.com">loans</a> and everything you need.
            2011-09-02 12:52 | CecileOneal25

            # re: C++的流設計很糟糕  回復  更多評論   

            很不錯。
            2011-09-08 23:25 | tall ugg outlet

            # re: C++的流設計很糟糕  回復  更多評論   

            你們都弱爆了,想想這樣的log api是怎么實現的

            jj_log(0, (%p, fd)(%d, backlog)((eh_format), er));

            jj_log(4, "ERROR: %d", er);
            2011-11-01 09:42 | tankxx

            # re: C++的流設計很糟糕  回復  更多評論   

            超級牛掰的一群人啊。。。佩服
            2011-11-23 13:07 | 煙圈
            久久精品夜色噜噜亚洲A∨| 亚洲?V乱码久久精品蜜桃| 亚洲国产成人久久一区久久| 成人久久精品一区二区三区| 亚洲精品无码成人片久久| 婷婷久久五月天| 色综合久久夜色精品国产| 久久国产精品二国产精品| 成人精品一区二区久久久| 国产精品午夜久久| 久久男人中文字幕资源站| 久久久久亚洲精品中文字幕| 亚洲中文字幕伊人久久无码| 久久久综合香蕉尹人综合网| 亚洲精品无码久久不卡| 亚洲日韩欧美一区久久久久我| 伊人 久久 精品| 狠狠综合久久AV一区二区三区| 久久国产劲爆AV内射—百度| 婷婷久久香蕉五月综合加勒比| 蜜臀久久99精品久久久久久小说| 精品无码久久久久久尤物| 99久久久久| 久久精品国产乱子伦| www性久久久com| 欧美午夜A∨大片久久| 97精品依人久久久大香线蕉97| 久久大香香蕉国产| 久久亚洲欧洲国产综合| 新狼窝色AV性久久久久久| 曰曰摸天天摸人人看久久久| 伊人热热久久原色播放www| 久久久婷婷五月亚洲97号色| 久久国产免费直播| 国内精品久久久久影院一蜜桃 | 精品国产日韩久久亚洲| 人妻久久久一区二区三区| 久久AAAA片一区二区| 99精品久久久久久久婷婷| 99久久精品久久久久久清纯| 麻豆AV一区二区三区久久|