2012-03-07 45 views
12

在我們的項目中,我們在對象模型中使用C++流操作符(< <)來打印出易讀的數據格式。一個簡單的例子是這樣的:如何將注入添加到流操作符

std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) { 
    oStream << "[SomeMember1: " << iOwnClass._ownMember1 << "]\n"; 
    oStream << "[SomeMember2: " << iOwnClass._ownMember2 << "]\n"; 
} 

在日誌中由此產生:

[SomeMember1: foo] 
[SomeMember2: bar] 

我們現在需要的是能夠縮進該操作的結果。有些調用類可能不需要像這樣的結果,但想在每行之前添加2個空格縮進。我們可以添加一個成員到我們的班級,指定縮進,但這似乎不是一個優雅的解決方案。

當然這不是一個很大的問題,但是如果這樣做能夠工作,我們的日誌記錄將會更好。

感謝

回答

21

最簡單的解決方案是在 ostream和實際的streambuf之間放置一個過濾streambuf。喜歡的東西:

class IndentingOStreambuf : public std::streambuf 
{ 
    std::streambuf*  myDest; 
    bool    myIsAtStartOfLine; 
    std::string   myIndent; 
    std::ostream*  myOwner; 
protected: 
    virtual int   overflow(int ch) 
    { 
     if (myIsAtStartOfLine && ch != '\n') { 
      myDest->sputn(myIndent.data(), myIndent.size()); 
     } 
     myIsAtStartOfLine = ch == '\n'; 
     return myDest->sputc(ch); 
    } 
public: 
    explicit   IndentingOStreambuf( 
          std::streambuf* dest, int indent = 4) 
     : myDest(dest) 
     , myIsAtStartOfLine(true) 
     , myIndent(indent, ' ') 
     , myOwner(NULL) 
    { 
    } 
    explicit   IndentingOStreambuf(
          std::ostream& dest, int indent = 4) 
     : myDest(dest.rdbuf()) 
     , myIsAtStartOfLine(true) 
     , myIndent(indent, ' ') 
     , myOwner(&dest) 
    { 
     myOwner->rdbuf(this); 
    } 
    virtual    ~IndentingOStreambuf() 
    { 
     if (myOwner != NULL) { 
      myOwner->rdbuf(myDest); 
     } 
    } 
}; 

要插入,只需要創建一個流緩衝的一個實例:

IndentingOStreambuf indent(std::cout); 
// Indented output... 

indent超出範圍,一切恢復正常。

(用於記錄,我有一個更復雜一些: LoggingOStreambuf需要__FILE____LINE__作爲參數,設置 myIndent到一個格式化字符串這些參數,再加上時間 郵票,它復位於凹坑每次輸出後的字符串,收集 所有的輸出在std::ostringstream,並且在析構函數原子 輸出到myDest

+0

工作完美!我做了一些更改,例如添加increaseIndent和decreaseIndent方法。我的日誌現在看起來完全是我想要的。謝謝。 – 2012-03-07 14:04:25

+0

@James:請問你有更復雜的代碼嗎? – Cookie 2014-06-05 08:32:41

1

不太好辦法做到這一點是添加一個全局變量,它告訴壓痕。例如:

std::string OwnClassIndentation; 
std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) { 
    oStream << "[SomeMember1:" << OwnClassIndentation << iOwnClass._ownMember1 << "]\n"; 
    oStream << "[SomeMember2:" << OwnClassIndentation << iOwnClass._ownMember2 << "]\n"; 
} 

然後根據情況進行設置。

+2

如果你只輸出特定類型,其操作''<<你定義的時候要縮進,那麼你可以使用'std :: ios_base :: xalloc'和'std :: ios_base :: iword' t o維持每個流的縮進。但是,即使在輸出'dest <<「某個字符串」'「時,如果您想要縮進,您需要使用過濾streambuf。 – 2012-03-07 11:39:58

+0

@JamesKanze:好點 - 同樣適用於我的解決方案 - 沒有想到這一點。 – 2012-03-07 11:41:24

+0

這肯定會工作,但我真的不喜歡全局變量,尤其是當他們只需要格式化我的日誌:) – 2012-03-07 14:06:34

0

您可以使自己的流類具有縮進變量並覆蓋該類的endl,插入縮進。

+2

這將不會縮進第一個輸出(可能不是特徵),會在最後一行後面插入空格(如果文本在文本模式下打開,則爲未定義行爲),並且當輸出''\ n''時不會縮進。當然,你也必須定義所有的'<<'運算符。這是'streambuf',而不是'ostream'類的工作。 – 2012-03-07 11:37:46

+0

我也想過這個,但我不得不寫信給我的ostream不在我手中,所以我不能改變它的類型。我必須使用我們日誌庫中的ostream。 streambuf工作。 – 2012-03-07 14:07:51

+0

@W。 Goeman:你可以從ostream中派生出來,它有虛擬方法。 – Dani 2012-03-07 16:25:32

1

這可以使用自定義的流操縱器來完成,該流操縱器將所需的縮進級別存儲在流的內部可擴展數組的字中。您可以使用功能ios_base::xalloc來請求這樣一個詞。這個功能會給你你單詞的索引。您可以使用ios_base::iword訪問它。一種方式來實現,這將是這樣的:

struct indent { 
    indent(int level) : level(level) {} 
private: 
    friend std::ostream& operator<<(std::ostream& stream, const indent& val); 

    int level; 
}; 

std::ostream& operator<<(std::ostream& stream, const indent& val) { 
    for(int i = 0; i < val.level; i++) { 
     stream << " "; 
    } 
    return stream; 
} 

std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) { 
    oStream << indent(oStream.iword(index)) << "[SomeMember1: " << 
       iOwnClass._ownMember1 << "]\n"; 
    oStream << indent(oStream.iword(index)) << "[SomeMember2: " << 
       iOwnClass._ownMember2 << "]\n"; 
} 

你必須弄清楚在哪裏存儲index。這實際上允許您將自定義狀態添加到流中(請注意,這不會是線程安全的開箱即用)。每個想要縮進的函數都應該將請求的縮進添加到流中,並在完成時再次減去它。你可以確保這始終使用保護加/減所需的縮進(恕我直言,這是比使用機械手更優雅)發生:

class indent_guard { 
public: 
    indent_guard(int level, std::ostream& stream, int index) 
    : level(level), 
     stream(stream), 
     index(index) 
    { 
     stream.iword(index) += level; 
    } 

    ~indent_guard() { 
     stream.iword(index) -= level; 
    } 

private: 
    int level; 
    std::ostream& stream; 
    int index; 
}; 

你可以使用這樣的:

void some_func() { 
    indent_guard(2, std::cout, index); 

    // all output inside this function will be indented by 2 spaces 

    some_func(); // recursive call - output will be indented by 4 spaces 

    // here it will be 2 spaces again 
} 
+0

至少你知道'iostream' :-)。這顯然是處理任何自定義類的特殊操作符的方式。如果第一個輸出不是由您自己定義的類型,這並沒有幫助;在這種情況下,您需要攔截'streambuf'級別的輸出。 – 2012-03-07 11:42:45

+0

這並沒有完全解決我的問題,但肯定是一個「很好的瞭解」。謝謝。 – 2012-03-07 14:02:54

+0

你的'some_func()'會導致堆棧溢出。 – uckelman 2013-08-24 11:50:04