標籤:

c++ 在使用vector::push_back時是否需要使用try...catch...包裹起來?

vector::push_back可能會發生異常,比如std::bad_alloc,那我們在實際的代碼編寫過程中是否需要在每次push_back時使用try...catch?補充:這裡面我最大的問題是我們是否讓程序出core,大家說的catch了,然後呢,然後可以把該釋放的釋放了,該寫的log信息寫了,程序繼續運行,不會出core,我想知道優秀的工程實踐是怎麼做的,優缺點是什麼


你怎麼保證你的錯誤處理程序(比如打log)不會內存溢出呢?

並不是所有的異常都是為了被捕獲而存在的,許多異常之所以是異常就是為了讓程序直接退出

另外,在許多操作系統(如Linux)上,並不是內存不足就一定會發生bad_alloc,內存不足有可能出現各種各樣的情況,比如說有些頁可能只是分配了虛擬內存地址而沒有分配實際物理內存,然後實際物理內存不足;有些頁可能是寫時複製的,寫時複製導致內存不足;還有各種跟swap有關的機制。這導致你的程序很可能根本不會出現在new的時候拋出異常這麼naive的情況,而是在訪問某個地址的時候沒有任何預兆地crash了,或者被OOM Killer直接殺掉了,或者更糟糕的,OOM Killer把系統關鍵進程殺掉了。所以捕獲bad_alloc真的毫無實際意義。就連你最有把握的「先分配一個超大的內存空間,如果bad_alloc就分小一點」也有可能直接觸發OOM Killer然後把自己殺掉,而沒有捕獲異常的機會。


基本上,一般情況下,C++ 中只有繼承自 std::runtime_error 的異常必須捕獲並處理。自定義的可恢復的錯誤,一般也應該繼承 std::runtime_error。其他異常,要麼是說明你的程序有 bug,比如調用 std::vector&::at() 時越界,要麼是無法恢復的,比如 std::bad_alloc。


如果有需要你當然可以處理

處不處理異常是應該根據你函數承諾提供的行為來決定的

void * fun() noexcept {
try {
void * p = new int[1];
return p;
} catch (...) {
return nullptr;
}
}

這裡你承諾了fun是noexcept的

那你就應該處理一切可能拋出的異常

包括bad_alloc

也就是說外部調用者看來這個函數任何情況下都是無異常的

任何結果都是這個函數的良定結果

處理異常的代碼盡量不要再去調用可能拋出異常的代碼

不然在處理異常代碼中還得處理這些可能被拋出的異常

所以你所謂的寫log 你得先處理完寫log的所有異常才行

不然你會發現你的程序不是因為你push_back的bad_alloc崩潰的

而是因為你的寫log本身發生的其他未捕獲異常而崩潰的

就bad_alloc這個異常的性質而言在應用層是沒有有效處理手段的

所以處理一般就是簡單的像上面一樣返回一個良定結果

你所謂的重試是意義不大的

void * fun() noexcept {
S:
try {
void * p = new int[1];
return p;
} catch (std::bad_alloc) {
goto S;
} catch (...) {
return nullptr;
}
}

這裡承諾不拋異常 遇到bad_alloc就無限重試 這可能導致這個函數永遠阻塞

如果你想重試一定次數後再拋異常

void * fun() {
int n = 0;
S:
try {
void * p = new int[1];
return p;
} catch (std::bad_alloc) {
if (n++ &< 3) goto S; throw; } catch (...) { return nullptr; } }

這裡不承諾不拋異常 該函數可能拋出bad_alloc異常 其他情況下都是良定結果

遇到bad_alloc重試三次 不行就重拋出

我只能說99.999%的情況下這種重試只是浪費時間而已

最終還是會重新把這個異常往上拋

還有就是你要記住不是你不捕獲異常程序就會崩潰

你不捕獲還有你的調用方呢

一般來說大部分異常都是不需要去捕獲的

都應該把責任推給調用方

調用方再推給它的調用方

直到推無可推的最上層邏輯才考慮是否處理和如何處理

一般來說只要不是最頂層邏輯你都可以肆無忌憚的無視異常


catch 了,然後呢?


出現bad_alloc,證明你基本上已經用光內存了,你救回來也改變不了什麼,下一步仍然會觸發bad_alloc,不如就這麼掛掉。


不用,就讓它crash。

try ... catch ...適用於是還可以搶救一下的情況。push_back都不行了,那已經沒得救了。不需要單獨catch這個情況。


應該catch的是那種業務邏輯的錯誤,比如網路連接斷了,資料庫事務失敗,用戶輸入非法,文件句柄耗盡,或者用戶代碼拋出異常之類。

無論你怎麼仔細修bug,也無法避免這種異常發生,這種錯誤一般來說就是catch一下,清理現場,回滾到正常狀態。

badalloc一般是由於程序寫錯了造成的。也沒法搶救,不如不捕獲,直接掛掉


我覺得首先你要明白,std::vector::push_back 能拋出的異常不止 std::bad_alloc。你需要考慮的不是「如果內存不足是否需要處理、如何處理」而是「如果追加元素的操作失敗是否需要處理、如何處理」。

端正了這一態度,問題就很好回答:

  • 調用 push_back 的函數本身,業務邏輯中是否有針對「追加元素失敗」的處理辦法?
    • 有:catch 住對應異常後,按照既定邏輯處理即可 (1)
    • 沒有:那麼是否有表示操作失敗的辦法(或者能改成有)?
      • 沒有辦法表示操作失敗
        • catch (...) 住然後記日誌,最後用 throw 重新拋出來,愛咋咋地,早死早超生
      • 允許用異常以外的方式表示操作失敗(如返回值、輸出參數等)
        • catch 住以後轉換為對應的錯誤輸出即可 (4)
      • 允許用異常表示操作失敗
        • 如果該函數對於拋出的異常有特定要求:catch 住以後轉換為符合要求的異常再 throw (3)
        • 如果該函數對於拋出的異常沒有特定要求:不用 catch (2)

在一個正常的支持異常的系統里,絕大多數情況下會選擇 (1) (2),少數臨界代碼會選擇 (3) (4)。


至於為什麼即便是 std::bad_alloc 也能夠存在合理的處理辦法: 邱昊宇:muduo使用std::vector調用resize等未捕獲bad_alloc,生產中真的沒問題么?


Let it crash.


如果你知道有什麼辦法能緩解內存不夠的情況,比如暫停掉程序給伺服器再插一條內存殺幾個進程然後繼續,那你就catch然後去做這些事情,再恢復

不過我覺得你多半沒有,那就不要catch


muduo使用std::vector調用resize等未捕獲bad_alloc,生產中真的沒問題么?


不要妄想都OOM了你區區一介用戶態程序能靠抓異常解決。最好的解決方式就是自爆或者等死,沒什麼好招。


不要。

即使不說從原理角度bad_alloc觸發可能性有多低,僅僅從工程角度來說

這種exception留出來讓程序自己崩了並且給出崩潰原因,比強行catch讓程序勉強運行下去要好的多的多。

前者起碼你知道程序是怎麼崩的哪裡崩的,而後者的話程序最終崩潰你會一頭霧水根本找不到原因


try
{
v.push_back(i);
}
catch(...)
{
throw MyException("No memory.");
}

這樣?


理論上所有的new都要catch,但是你會去這麼寫嗎


不用

push_back屬於很保險的東西了,這都能內存出錯,那估計已經很麻煩了,不如重啟試試


這時候失敗的時候解決辦法不是讓進程不退出

而是1.回收內存

2.讓運維提高內存

3.為啥內存會佔用這麼高,解決(是否是內存泄漏等


不需要


你都使用std::vector了,就說明了不用管resize 的異常了。如果你真的覺得必須要管std::vector的resize,那你可能根本就不會在項目中使用stl。


c++內存爆了的話,當然最好是讓程序掛了,不掛的話,你還能做什麼?


用戶態的話,內存不足應視為非受檢的。Java 這塊就做的不錯。

以上。


exceptional c++ style上有關於new是否需要捕捉異常的論述,和你這個問題比較類似,可以參考一下(僅做回憶性描述,不保證準確):

1、如果出現內存耗盡異常的話,怎麼處理才是完美的,實際上一旦內存耗盡,無法完美的保存所有的狀態信息,不如讓他異常退出,事後可以根據core文件查看異常的原因。

2、既然是內存異常,那屬於運維部門的責任,內存使用監控是現代軟體公司運維部門必備的監控點,所以實際中不會出現也不應該出現內存耗盡這種異常情況


推薦閱讀:

C++ 需要 restrict 關鍵字嗎?
在校學生深入學習QT後會不會找不到比較好的工作?
面試 C++ 被人問你是如何優化你的代碼的,該從哪些方面進行回答?
為什麼 C++ 標準不明確二進位介面 (ABI) 標準?

TAG:C |