2016-05-12 50 views
0

我想開發一個Exception類,它允許收集相關的數據流風格。我可以扔流嗎?

Custom stream to method in C++?我伸出我自己的類:

class NetworkException : public std::exception, public std::ostream 

從流挑錯誤的數據,然後返回無論它通過.what()收購。

然後我試圖這樣的事情:

try { 
    ssize_t ret = send(sock, headBuffer, headLength, MSG_MORE); 

    if (ret <= 0) throw NetworkException() << "Error sending the header: " << strerror(errno); 

    // much more communication code 

} catch (NetworkException& e) { 
    connectionOK = false; 
    logger.Warn("Communication failed: %s",e.what()); 
} 

但是,編譯器產生一個錯誤:

HTTPClient.cpp:246:113: error: use of deleted function 'std::basic_ostream<char>::basic_ostream(const std::basic_ostream<char>&)' 

(這是與throw行了。)

我知道流別沒有拷貝構造函數,但我認爲捕獲引用而不是對象就足夠了。我該如何克服這個問題 - 我可以拋出一個'吞食'流的物體嗎?

+0

[C++?:拋出異常就會調用複製構造]的可能的複製(http://stackoverflow.com/questions/10855506/c-throwing-an-exception-invokes-the-copy-constructor) –

+0

@MohamadElghawi:那麼,這成爲「我可以以某種方式爲流對象創建一個拷貝構造函數嗎?」 –

回答

1

您不能複製使用默認的拷貝構造函數包含流對象的對象,但你可以編寫自己的拷貝構造函數,將複製流的內容。

#include <iostream> 
#include <sstream> 

struct StreamableError : public std::exception { 
     template <typename T> 
     StreamableError& operator << (T rhs) { 
      innerStream << rhs; 
      return *this; 
     } 

     StreamableError() = default; 

     StreamableError(StreamableError& rhs) { 
       innerStream << rhs.innerStream.str(); 
     } 

     virtual const char* what() const noexcept { 
      str = innerStream.str(); //this can throw 
      return str.c_str(); 
     } 

    private: 
     std::stringstream innerStream; 
     mutable std::string str; 
}; 


int main() { 
     try { 
       throw StreamableError() << "Formatted " << 1 << " exception."; 
     } catch (std::exception& e) { 
       std::cout << e.what() << std::endl; 
     } 
} 

以上的解決方案並不完美。如果str = innerStream.str()拋出然後std::terminate將被調用。如果你想what方法是真實地noexcept那麼你有兩種選擇:

  1. 流將內容複製到str變量拷貝構造函數中。然後,您將無法在拋出之前調用what方法來獲取異常消息。
  2. 將流的內容複製到str變量裏面的複製構造函數和<<運算符。在這種情況下,您將能夠在投擲前獲得異常消息,但是每次調用運算符時都會複製消息,這可能是一種矯枉過正的行爲。
+0

如果一個愚蠢的短字符串的普通副本拋出,那麼一般情況是災難性的,以至於成功完成異常並不會有任何幫助。在這種情況下,程序不會像平常那樣恢復正常的運行,或者情況的原因可能會接近這個例外。它將一支口紅貼在屍體上 - 如果我不能依靠複製50個人物的能力,那麼系統能夠做的最好的事情就是讓看門狗起作用。在調試器下,它無關緊要。無需調試器,無論如何它都無法恢復。 –

1

異常總是至少複製一次。通過引用捕獲異常避免了第二個副本。

只是一個想法:也許你可以嵌入你的流在智能指針,如std::shared_ptr,然後扔掉那個智能指針。

我個人通常使用std::runtime_error無處不在。

+2

個人而言,我避免使用'std :: runtime_error'這樣的''異常,因爲它們[在施工過程中可能會拋出](http://stackoverflow.com/q/36106747/3919155)。 – jotik

+0

@jotik真的。無論如何,異常在C++中被破壞了。 – ZunTzu

1

除了ZunTzu的回答:使用ostringstream代替。

::std::ostringstream what; 
what << "Error sending the header: " << strerror(errno); 
throw ::std::exception(what.str()); 

其實::性病::例外,沒有構造以字符常量*或::的std :: string常量&,所以您可能需要使用適當的現有subclass或創建自己的一個。

1

你想做的事情已經被很多人嘗試過了。當然這是可能的,但需要一些技巧(類似於製作流式記錄器所需的技巧)。

它還原來是一個壞主意,因爲:

  1. 它情侶流爲例外的概念的概念。

  2. 可以更簡單地完成了一個單一的模板函數

其實,這裏有3點非常簡單的選擇:

#include <iostream> 
#include <sstream> 
#include <exception> 
#include <stdexcept> 
#include <boost/format.hpp> 

template<class...Args> 
std::string collect(Args&&...args) 
{ 
    std::ostringstream ss; 
    using expand = int[]; 
    void(expand{0, ((ss << args), 0)...}); 
    return ss.str(); 
} 

struct collector : std::ostringstream 
{ 
    operator std::string() const { 
     return str(); 
    } 
}; 

// derive from std::runtime_error because a network exception will always 
// be a runtime problem, not a logic problem 
struct NetworkException : std::runtime_error 
{ 
    using std::runtime_error::runtime_error; 
}; 

int main() 
{ 
    try { 
     throw NetworkException(collect("the", " cat", " sat on ", 3, " mats")); 

    } catch (const std::exception& e) { 
     std::cout << e.what() << std::endl; 
    } 

    try { 
     throw NetworkException(collector() << "the cat sat on " << 3 << " mats"); 

    } catch (const std::exception& e) { 
     std::cout << e.what() << std::endl; 
    } 

    try { 
     throw NetworkException((boost::format("the cat sat on %1% mats") % 3).str()); 

    } catch (const std::exception& e) { 
     std::cout << e.what() << std::endl; 
    } 


    return 0; 
} 

預期輸出:

the cat sat on 3 mats 
the cat sat on 3 mats 
the cat sat on 3 mats 

最後,可能是最流的解決方案:

template<class Exception> 
struct raise 
{ 
    [[noreturn]] 
    void now() const { 
     throw Exception(_ss.str()); 
    } 

    std::ostream& stream() const { return _ss; } 

    mutable std::ostringstream _ss; 
}; 

template<class Exception, class T> 
const raise<Exception>& operator<<(const raise<Exception>& r, const T& t) 
{ 
    using namespace std; 
    r.stream() << t; 
    return r; 
} 

struct now_type {}; 
static constexpr now_type now {}; 

template<class Exception> 
void operator<<(const raise<Exception>& r, now_type) 
{ 
    r.now(); 
} 

調用站點例如:

raise<NetworkException>() << "the cat " << "sat on " << 3 << " mats" << now; 

我用定點now,以避免任何討厭的析構函數在暗中搗鬼。

+0

我認真考慮在'collector class'方法上作弊一點:重載異常的'operator <<(collector&)',而不使它成爲任何「流」的子節點。這樣它看起來就會在語法上一致,收集器將被視爲轉換器爲「可拋式」類型,並且該異常不會與任何外來概念緊密結合。 –

+0

@SF。嗯,你給了我一個主意...... –

+0

@SF。最後見第四個選項。 –

1

Can I throw a stream?

不,你不能。當引發異常時,將從異常表達式構造一個臨時對象。由於流對象不能從另一個流對象構造,所以流對象不能被拋出。

從C++ 11標準:

15.1 Throwing an exception

3 A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from 「array of T」 or 「function returning T」 to 「pointer to T」 or 「pointer to function returning T」, respectively. The temporary is an lvalue and is used to initialize the variable named in the matching handler (15.3).