雷同問題:https://www.zhihu.com/question/22889420

問題已解決,感謝大家的指導!!萬分感謝

//////////

本人初次使用異常處理,但是寫出來的代碼感覺一點都不清晰,請教一下大家,有沒有辦法解決

就像圖中,第一個try塊的new,第二個try塊的string operater=,第三個try塊的return調用拷貝構造函數,都有可能因為內存不足發生std::bad_alloc,或者其他未知的異常(因為我不知道是啥異常,所以捕獲異常類std::exception)。我希望他如果出現異常,直接返回一個空得string對象。


這個不是C++的鍋,按你的寫法到Java上也是一樣混亂。

try-catch的首要原則是只catch你能處理且你想處理的異常

bad_alloc就是典型的絕大多數情況下沒法處理的異常。(其實你會不會收到這個異常都難說)

次要原則是劃定try的範圍,合併相同的處理方式。如果你不管遇到什麼錯誤都是返回一個空string,那在函數最外層包一個try catch不就行了。

此外我覺得你對C++的理解是有問題:

  • 最裡面的try{return}catch很可能是沒意義的,這個異常很可能是在函數返回後拋的,你這裡接不住。
  • 先new char再遞給string的做法多此一舉,string resize就行了,而且你new完也沒檢查是不是nullptr。(nullptr這個錯的,一時腦抽想成malloc了)

最後還有一個問題:既然你把異常都catch住了,那調用方看見返回一個空字元串時,怎麼知道是哪裡錯了呢?

如果你不打算讓調用方看到/知道發生了錯誤,那除了try catch外,不如再加個noexcept。


寫成這樣的最大原因其實是你在試圖手工管理內存,請用類似 std::vector 的東西。

另外,無論是否使用異常,大多數情況下你的需求都是統一處理「出錯」情況。除非你有針對「內存分配出錯」的特殊處理需求,否則不要 catch bad_alloc

比如你想實現 UTF16ToUTF8 這個函數不拋出任何異常,發生任何出錯情況都返回空字元串的話,在函數層面 try 一下就足夠了。例如:

std::string UTF16ToUTF8(wchar_t const *pWideBytes, int cchChar) noexcept
try {
int const count = WideCharToMultiByte(CP_UTF8, 0, pWideBytes,
cchChar, NULL, 0, NULL, NULL);
if (count == 0) {
return {};
}

std::vector& buffer(count);
if (WideCharToMultiByte(CP_UTF8, 0, pWideBytes, cchChar,
buffer.data(), buffer.size(), NULL, NULL) &< 0) { return {}; } return {std::begin(buffer), std::end(buffer)}; } catch (std::exception const) { return {}; }

當然,返回空字元串並不是好的錯誤處理方法,因為空字元串也可以作為參數,調用方甚至沒法根據返回值是否為空得知是否出錯。

好的處理方法是在這個函數里根本不要 try catch。原因並不是我們常聽說的「內存不足就應該直接崩潰」,而是這裡的出錯情況應該通知並交給外部處理,到底是崩潰還是有其它處理方案,你在當前這個函數里是不應該知道的。

// 使用異常
std::string UTF16ToUTF8(wchar_t const *pWideBytes, int cchChar);

// 錯誤作為輸出參數
std::string UTF16ToUTF8(wchar_t const *pWideBytes, int cchChar, Error err) noexcept;

// 錯誤作為返回值,結果作為輸出參數
Error UTF16ToUTF8(wchar_t const *pWideBytes, int cchChar, std::string out) noexcept;

// 奇行種
void UTF16ToUTF8(wchar_t const *pWideBytes, int cchChar, std::string out, Error err) noexcept;

反正我選第一種。


先new再複製給std::string是什麼鬼才操作。

int nUtf8Count = WideCharToMultiByte(CP_UTF8, 0, pWideBytes, cchChar, nullptr, 0, nullptr, nullptr);
if (nUtf8Count &<= 0) { throw std::system_error(GetLastError(), std::system_category(), __func__); } std::string s; s.resize(nUtf8Count); if (WideCharToMultiByte(CP_UTF8, 0, pWideBytes, cchChar, s.data(), nUtf8Count, nullptr, nullptr) &<= 0) { throw std::system_error(GetLastError(), std::system_category(), __func__); } return s;

想返回空字元串就在外麵包上try { /* 原來內容 */ } catch (...) { return std::string(); },不過個人不建議,因為空字元串是正常值。

~~~~

即使古代std::string不能直接用data(),也不需要手動管理內存啊。既然會用std::string會catch,應該也學過寫RAII包裝了吧。

int nUtf8Count = WideCharToMultiByte(CP_UTF8, 0, pWideBytes, cchChar, NULL, 0, NULL, NULL);
if (nUtf8Count &<= 0) { throw system_error(GetLastError(), system_category()); } struct RAIIBuffer { // unique_ptr& in C++11
RAIIBuffer(char* ptr) { this-&>ptr = ptr; }
char* get() { return ptr; }
~RAIIBuffer() { delete[] ptr; }
private:
char* ptr;
RAIIBuffer(const RAIIBuffer);
void operator=(const RAIIBuffer);
};
RAIIBuffer buf(new char[nUtf8Count]);
if (WideCharToMultiByte(CP_UTF8, 0, pWideBytes, cchChar, buf.get(), nUtf8Count, NULL, NULL) &<= 0) { throw system_error(GetLastError(), system_category()); } return std::string(buf.get(), nUtf8Count);


內存不足,程序應該直接崩潰,繼續運行會出現更多錯誤。

所以,對內存不足的最佳應對就是不要捕捉異常,直接讓程序掛掉。

沒有異常機制之前,我們需要檢查內存分配的返回值,就是怕內存出錯之後繼續運行,需要手工捕捉內存分配失敗儘早退出應用。有異常之後程序機制直接幫你退出進程了,你就別多事了。

現代應用的理念是:不怕掛,就怕卡。伺服器掛了自然有熱備的伺服器頂上,桌面掛了用戶自然會重啟。

但是內存不足的時候基本就是交換空間也滿了,整個系統響應極慢,只有退出大量程序才能解決。這種時候你非要保證程序運行不退出,那你這個應用不是跟毒瘤似的么。

就算你的程序是嵌入式,需要保證常開,常用的方法也是用看門狗監控它掛掉就立即重啟,而不是不讓它崩潰。

--

退一萬步說,如果你的程序真的不能退出,那麼你要解決的也是不讓內存不夠這件事情發生。而不是在發生之後假裝當做不知道。


異常處理兩個原則,已知,可解決。

你這裡的問題是違背第二個原則,可解決。內存不夠是你程序無法解決的問題,所以不應該捕獲異常。

什麼異常是可解決的?例如你打開一個文件失敗了,你可以過一會兒再試一次,說不定就成功了,這就是可解決的。

那既然內存溢出是無法解決的問題,為什麼要設計這個異常呢?

因為有人可以解決。

譬如說宿主或者應用程序,他們提示給用戶或者記錄個日誌,內存溢出導致程序退出就完了……


推薦閱讀:
相关文章