2012-10-28 59 views
4

對不起,標題不知道該怎麼稱呼我想要做的事。自動換行ostream

一些背景知識,如果您不喜歡閱讀,請跳到下一段。我有一個單元測試類,我調用斷言的一些條件,如果失敗,我輸出一些字符串傳入英寸我發現這是非常煩人的建立一個字符串發送到這個,如果我想說"Failed on index: " + i 。我的想法是返回std::ostream而不是採取std::string。如果斷言失敗,我將返回std::cerr,如果斷言通過,則返回std::stringstream。我想我可以做到這一切都很好。我不得不在我的單元測試類中存儲一個std::stringstream,所以我可以返回一個引用。

我想要做的不是返回一個標準std::ostream而是返回一個擴展的std::ostream,它在輸出時輸出std::endl,所以我不必爲每個斷言記住它。在特別的想法是這樣的:

UnitTest("My test"); 
ut.assert(false) << "Hello world"; 
ut.assert(1 == 0) << "On the next line"; 

的想法是,在破壞這個新的類將輸出的底線,它會只要它不再使用被破壞(即沒有更多< <運營商) 。到目前爲止,這是我(我已經刪除了一些在斷言的代碼,它實際上是一個類裏面,但是這足以說明這是怎麼回事):

class newline_ostream : public std::ostream 
{ 
    public: 
    newline_ostream(std::ostream& other) : std::ostream(other.rdbuf()){} 
    ~newline_ostream() { (*this) << std::endl; } 
}; 

newline_ostream& assert(bool condition, std::string error) 
{ 
    if(!condition) 
    { 
     return newline_ostream(std::cerr); 
    } 

    return newline_ostream(std::stringstream()); 
} 

當我試試這個方法我基本上告訴我,返回一個我剛創建的對象是錯誤的,因爲它不是左值。當我嘗試改變它不返回一個引用它抱怨沒有複製構造函數(大概這是因爲我擴展std::ostream,它沒有複製構造函數)。

我正在尋找的是一些導致編譯器創建一個臨時newline_ostream是assert()將盡快它不再使用(即不超過< <運營商)寫它導致成將死法。這是可能的,如果是這樣的話?

回答

2

複製std::cerr是不可能的(刪除std::basic_ostream的複製構造函數)。因此,創建一個實現複製構造函數的派生類並不是真正的選擇。

我建議你創建你newline_ostream作爲一類包含引用(而不是源自)std::ostream

#include <iostream> 

class newline_ostream 
{ 
    std::ostream &_strm; 
public: 

    explicit newline_ostream(std::ostream &strm) 
    :_strm(strm) 
    {} 

    /* In the destructor, we submit a final 'endl' 
    before we die, as desired. */ 
    virtual ~newline_ostream() { 
    _strm << std::endl; 
    } 

    template <typename T> 
    newline_ostream &operator<<(const T& t) { 
    _strm << t; 
    return *this; 
    } 
}; 

int main() 
{ 
    newline_ostream s(std::cerr); 
    s << "This is a number " << 3 << '\n'; 

    /* Here we make a copy (using the default copy 
    constructor of the new class), just to show 
    that it works. */ 
    newline_ostream s2(s); 
    s2 << "This is another number: " << 12; 

    return 0; 
} 
+0

你說我不能做一個拷貝構造函數讓我思考,我想出了我的答案。您是否介意評論它是否有任何問題(如不能攜帶)? – CrazyCasta

0

您也可以使用預處理此:

#define U_ASSERT(ut, cond, stream) \ 
    do { ut.assert(cond) << stream << std::endl; } while (0) 

U_ASSERT(ut, 1 == 0, "The result is " << (1 == 0)); 

但是,這個和你已經使用的方法(通過jogojapan的修改)基本上是唯一的選擇。這是因爲即使您開始使用緩衝區,您也無法知道何時執行了一個輸出操作,並且下一次啓動,因此您不知道何時放入換行符。

+0

只是,我想出了一個適合我的解決方案。讓我知道是否有任何你看到錯誤的,但。 (我假設用戶不會存儲newline_ostream)。 – CrazyCasta

0

我發現對我的作品(特別是我返回副本,而不是引用)的方式:

class newline_ostream : public std::ostream 
{ 
    public: 
    newline_ostream(const std::ostream& other) : std::ostream(other.rdbuf()){} 
    newline_ostream(const newline_ostream& other) : std::ostream(other.rdbuf()){} 
    ~newline_ostream() { (*this) << std::endl; } 
}; 

newline_ostream assert(bool condition, std::string error) 
{ 
    if(!condition) 
    { 
     return newline_ostream(std::cerr); 
    } 

    return newline_ostream(nullStream); // nullStream defined elsewhere (in the class) 
} 

用法:

ut.assert(ps.getFaces()[i] == expected[i], ss.str()) << "Test " << i; 

輸出:

Test 0 
Test 1 
Test 2 
Test 3 
Test 5 
Test 7 
Test 9 
Test 10 
+1

你應該詳細說明你做了什麼(不再返回引用) –

+0

我認爲複製流緩衝區是可能的,基本上是一個好主意。據我所知,當你從'std :: cerr'創建'newline_ostream'時,這應該起作用。然而,當你在'assert'函數的原始版本中創建臨時的'std :: stringstream'時,你會遇到麻煩。現在你有了'nullStream',我認爲這是一個全局對象。我有點擔心如果您重置該全局字符串流(例如,通過調用'nullStream.str(「」);')會發生什麼情況。這可能會使現有'newline_ostream'對象持有的流緩衝區指針失效。 – jogojapan

+0

無可否認,我原來的答案根本沒有解決這方面的問題。它只談論如何使複製構造函數成爲可能;所以如何確保字符串流(如果使用了一個而不是'cerr')的問題是一個單獨的問題。 – jogojapan

2

可能是一個異端,而是派生出一個流來產生另一個流類型,一種更普遍的方式可以是定義一個操縱器:

// compile with g++ -std=c++11 -Wall -pedantic 

#include <iostream> 

class sassert 
{ 
public: 
    sassert(bool b) :ps(), good(b) 
    {} 

    friend std::ostream& operator<<(std::ostream& s, sassert&& a) 
    { a.ps = &s; if(a.good) s.setstate(s.failbit); return s; } 

    ~sassert() 
    { 
     if(good && ps) ps->clear(); 
     if(!good && ps) *ps << std::endl; 
    } 

    //move semantics allow sassert to be a result of a calculation 
    sassert(sassert&& s) :ps(s.ps), good(s.good) { s.ps=nullptr; } 
    sassert& operator=(sassert s){ ps=s.ps; good=s.good; s.ps=0; return *this; } 
private: 
    std::ostream* ps; 
    bool good; 
}; 

int main() 
{ 
    std::cout << sassert(false) << "this is a failed assertion"; 
    std::cout << sassert(true) << "this is a good assertion"; 
    std::cout << sassert(false) << "this is another failed assertion"; 
    std::cout << sassert(true) << "this is another good assertion"; 
    return 0; 
} 

將運行產生

this is a failed assertion 
this is another failed assertion 
+0

如果這是一個異端,它絕對是一個非常聰明的。 +1。 – jogojapan

+0

@jogojapan:雖然和所有聰明的想法一樣,但它們與慣例相違背。沒有理由說「流」對象不能檢查流的狀態,例如在失敗時拋出異常。 –

+0

@MatthieuM。我懷疑所有聰明的想法都這樣做......但無論如何。有關使用故障位的已有慣例是什麼?當故障位(而不是壞位)被認爲合適時拋出異常? – jogojapan

1

這真的取決於你想要達到什麼樣的具體細節。

例如,如果您從未聽說過Type Tunneling,那麼可能是閱讀它的好時機。有一種方法使用墊片做一些瘋狂的事情...

否則,這裏是一個簡單的版本:

class AssertMessage { 
public: 
    AssertMessage(): _out(nullptr) {} 
    AssertMessage(std::ostream& out): _out(&out) {} 

    AssertMessage(AssertMessage&& other): _out(other._out) { other._out = nullptr; } 

    AssertMessage& operator=(AssertMessage&& other) { 
     if (_out) { _out << "\n"; } 
     _out = other._out; 
     other._out = nullptr; 
     return *this; 
    } 

    ~AssertMessage() { if (_out) { _out << "\n"; } } 

    template <typename T> 
    AssertMessage& operator<<(T const& t) { 
     if (_out) { *_out << t; } 
    } 

private: 
    std::ostream* _out; 
}; // class AssertMessage 

說明如何通過嵌入一個指針,我們並不需要一個全球性的「空」對象?這是指針和引用之間的主要區別。另外還要注意使用移動構造函數/移動賦值操作符以避免輸出新行或更多行。

然後,您可以編寫assert方法:

AssertMessage UnitTest::assert(bool i) { 
    return i ? AssertMessage() : AssertMessage(std::cerr); 
} 

但是....我會認真考慮使用宏,如果我是你,因爲你得到額外津貼:

#define UT_ASSERT(Cond_) \ 
    assert(Cond_, #Cond_, __func__, __FILE__, __LINE__) 

AssertMessage assert(bool test, 
        char const* condition, 
        char const* func, 
        char const* file, 
        int line) 
{ 
    if (test) { return AssertMessage(); } 

    return AssertMessage(std::cerr << 
     "Failed assert at " << file << "#" << line << 
     " in " << func << ": '" << condition << "', "); 
} 

然後你會得到這樣的東西:

Failed assert at project/test.cpp#45 in foo: 'x != 85', <your message> 

它是無價的,在大型測試套件,至少要有文件名和行號。

最後,一個宏可以讓你更加了解:如果在消息中調用一個函數,如ut.assert(x) << x.foo();,那麼即使消息不會被打印,也需要完全評估x.foo();這是相當浪費的。然而,隨着宏:

#define UT_ASSERT(Cond_, Message_) \ 
    while (!(Cond_)) { std::cerr << .... << Message_ << '\n'; break; } 

那麼如果條件計算結果爲true不執行的while身體都沒有。

+0

爲什麼我想用一段時間。看起來就像只會在無限循環中堵塞它。 – CrazyCasta

+0

@CrazyCasta:確切的說,我錯過了'break'。該問題與宏一般。如果(..)MACRO變成'if(...)stmt1;'如果(...)stmt1; stmt2; stmt3;'解析爲'if(...){stmt1; } stmt2; stmt3;'由編譯器。所以一般你把它們包裝在'do {...} while(0)'塊中;這是一個單獨的指令塊語法。在這裏,因爲我們需要一個'if',所以我們可以使用'while'代替,最後使用'break'(我們需要避免* dangling-else *問題)。 –