結局C++でexceptions-basedよりreturn-error-basedでコード書いているなという訳
はじめに
C++でコードを書いていてexceptions-based
よりreturn-error-based
でコードを書いているなと思ったので、その理由をメモ。皆様はどう思いますかね。
return-error-based
戻り値でエラーコードの情報を返すスタイル。授業でC言語から習った人が初めに見るのがこれ。
int h() {
int ret = f();
if (ret != OK) {
return GENERAL_ERR;
}
//...
return OK;
}
int
だけではなくenum
だったりbool
だったりする。最近ではstd::optional
やstd:: expected
が出てきたりもしている。
std::expected<int, std::string> idiv(int a, int b)
{
if (b == 0) {
return std::unexpected{"divide by zero"};
}
if (a % b != 0) {
return std::unexpected{"out of domain"};
}
return a / b;
}
void h()
{
auto v = idiv(10, 2);
if (v) {
std::cout << *v << std::endl;
} else {
std::cout << std::quoted(v.error()) << std::endl;
}
}
exceptions-based
エラー処理に例外を使う場合の書き方はC++ではこんな感じ
void h() {
try {
auto num = toInt("hogehoge");
std::cout << num << std::endl;
} catch (const std::runtime_error& e) {
std::cout << e.what() << std::endl;
}
}
例外が発生する部分をtry
で囲む。その区間で例外がthrow
されるとcatch
で補足されてエラー処理が行われる。
exceptions-basedのメリット
int f() {
bool b = g();
if (b != true) {
return GENERAL_ERR;
}
//...
return OK;
}
int h() {
f();
// fの戻り値の無視することは簡単
//...
return OK;
}
関数f
からすると自身が作った処理でもない、関数g
の中で発生したエラーを上位に伝える必要がありエラーの変換処理を都度かかなくてはいけない。また、関数h
で行われているようにエラーも無視が簡単すぎる。そこで例外処理を使うとわかりやすいコードを書くことができる。
void f()
{
//...
g();
//...
}
void foo()
{
try {
g();
}
catch (runtime_error& e) {
error_message(e.what());
}
}
エラーコードと異なり黙って見過ごされることがなく、自動的に呼び出し元に伝搬されていく、また、エラーコードではif
分で制御フロー中に例外ハンドリングのコードが混ざるが、例外ではcatch
の中に隔離することができる。あと、C++ではコンストラクタや演算子オーバーロードからの直接的なエラー通知手段としては優れている。
また、例外処理はエラーコードに比べて時間がかかるが、このデメリットも例外だからという理由より、そもそも頻繁に例外が発生する異常事態になっていること自体が問題だったりする。
それでもreturn-error-basedを使う理由
exceptions-based
のメリットを見ると、よほど高速なリアルタイム性が要求されるのでなければ採用すべきだろうが、結局、あまり採用しないことが多くて、理由を考えると、Cleaner, more elegant, and wrongやCleaner, more elegant, and harder to recognizeで言われているように単純に扱いが難しいからだと思う。
int h() {
f();
g(s());
return OK;
}
上のコードを見たときにreturn-error-based
で書いているプロジェクトなら、
「おいおい、戻り値とっていないじゃん。」
と直ぐ悪いコード気が付けるがexceptions-based
で書いていると
「上でエラー処理まとめてやっているのかな?」
と直ぐ気が付くことができないです。更にいうと戻り値を無視しているかは検査ツールで用意に検査できるため、うっかり補足し忘れたということはほぼないでしょう。また、C++で適用している領域がまさしく高速なリアルタイム性を要求していることが多いので大部分のエラー処理はreturn-error-based
を採用することになるのかなと思います。
参考情報
Cleaner, more elegant, and wrong
Cleaner, more elegant, and harder to recognize
P0323R10 std::expected
Discussion