ざっくり説明
例外対処例
// 23_intro_quick.cpp
#include <iostream>
#include <stdexcept>
using namespace std;
int divi(int a, int b)
{
if (b == 0)
{
throw runtime_error("0で割れない");
}
return a / b;
}
int main(void)
{
try
{
cout << divi(10, 2) << endl;
cout << divi(5, 0) << endl; // ここで例外
}
catch (const runtime_error &e)
{
cout << "エラー: " << e.what() << endl;;
}
}対応していないと…
try/throw/catch の基本とcatch順序エンジニアとしてのやることチェック
try / throw / catch
の基本を身につける。catch する順序とコツを理解する。throw;) を体験する。std::exception
系と独自例外クラスの使い分けを知る。new/delete より
vector やスマートポインタ)を実感する。// 23_try_basic.cpp
#include <iostream>
#include <stdexcept>
using namespace std;
int divide(int a, int b)
{
if (b == 0)
{
throw runtime_error("0で割ることはできません");
}
return a / b;
}
int main(void)
{
try
{
cout << divide(10, 2) << endl;;
cout << divide(5, 0) << endl;; // ここで例外
cout << "ここには到達しません\n";
}
catch (const runtime_error &e)
{
cout << "実行時エラー: " << e.what() << endl;
}
catch (...)
{
cout << "想定外の例外を捕捉" << endl;;
}
return 0;
}ポイント
throw で例外オブジェクトを投げる。catch (型)
はより具体的な型から並べる(順序が大事)。catch (...)
はどんな型でも受け止める保険。// 23_catch_order.cpp
#include <iostream>
#include <stdexcept>
using namespace std;
void f(int code)
{
if (code == 1)
{
throw out_of_range("範囲外");
}
else if (code == 2)
{
throw runtime_error("実行時");
}
else
{
throw exception();
}
}
int main(void)
{
try
{
f(1); // 1,2,その他 を変えて試す
}
catch (const out_of_range &e)
{
cout << "out_of_range: " << e.what() << endl;
}
catch (const runtime_error &e)
{
cout << "runtime_error: " << e.what() << endl;
}
catch (const exception &e)
{
cout << "exception: " << e.what() << endl;
}
}ポイント
std::exception
は最後に。// 23_rethrow.cpp
#include <iostream>
#include <stdexcept>
using namespace std;
void low()
{
throw runtime_error("lowで失敗");
}
void mid()
{
try
{
low();
}
catch (const runtime_error &)
{
// ログだけ取り、呼び出し元へそのまま再スロー
throw;
}
}
int main(void)
{
try
{
mid();
}
catch (const exception &e)
{
cout << "mainで捕捉: " << e.what() << endl;
}
}ポイント
throw;(オブジェクトを書かない)で元の例外を再スロー。// 23_raii_safety.cpp
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class Enemy
{
public:
explicit Enemy(int hp) : hp_(hp) {}
int hp() const { return hp_; }
private:
int hp_;
};
int main(void)
{
// 例外が起きても自動で後始末される安全な資源管理
vector<unique_ptr<Enemy>> es;
es.push_back(make_unique<Enemy>(10));
es.push_back(make_unique<Enemy>(20));
// 途中で例外が起きてもOK(ここでは例として投げる)
throw runtime_error("途中で失敗");
// 到達しないが、esはスコープ終了時に自動解放される
}ポイント
new/delete
は例外時にdelete漏れが起きやすい。vector や unique_ptr を使うのが基本方針。// 23_custom_exception.cpp
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
class InvalidHpError : public runtime_error
{
public:
explicit InvalidHpError(const string &msg) : runtime_error(msg) {}
};
class Enemy
{
public:
explicit Enemy(int hp)
{
if (hp < 0)
{
throw InvalidHpError("HPが負です: " + to_string(hp));
}
hp_ = hp;
}
private:
int hp_ = 0;
};
int main(void)
{
try
{
Enemy e(-5);
}
catch (const InvalidHpError &e)
{
cout << "InvalidHpError: " << e.what() << endl;
}
}ポイント
std::logic_error /
std::runtime_error
など)を継承して意味を明確に。what()
に詳細を入れて原因調査を早くする。catch
の中にブレークポイントを置き、捕捉→継続の流れを確認。体験タスク:
23_rethrow.cppを使い、low → mid → mainの順に例外が通過する様子を一歩ずつ観察する。
問1(23_q1.cpp) catch
の並びとして正しいものを選べ。
ア
catch(const exception&) → catch(const out_of_range&) → catch(...)
イ
catch(const out_of_range&) → catch(const exception&) → catch(...)
ウ
catch(...) → catch(const runtime_error&) → catch(const exception&)
エ
catch(const exception&) → catch(const runtime_error&) → catch(const out_of_range&)
問2(23_q2.cpp)
再スロー(throw;)について正しい説明を選べ。
ア throw e; と throw;
は常に同じ挙動である。
イ throw;
は直近で捕捉した例外をそのまま再スローする。
ウ throw; は新しい exception()
を投げ直す。
エ throw; は try の外でも使える。
問3(23_q3.cpp) 例外安全の観点で望ましい選択を選べ。
ア new で配列確保し、例外時は手作業で
delete[]。
イ vector<Enemy*> に new
で格納し、終了時に delete をループで実行。
ウ vector<Enemy> もしくは
vector<unique_ptr<Enemy>> を使う。
エ 例外は使わず exit(1) で終了する。
問4(23_q4.cpp) 次の divide
はどのように改善すべきか。
int divide(int a, int b)
{
if (b == 0)
{
// TODO: 改善
}
return a / b;
}ア return 0; にしておけば安全。
イ throw runtime_error("0で割れない");
として呼び出し側で catch。
ウ cout << "エラー"; を出して続行。
エ assert(b != 0); のみ。
目的: リソース読み込み失敗を例外で扱い、安全に後始末する。
loadTexture(const string&) を作成。拡張子が
.png 以外なら invalid_argument を
throw。main では
vector<unique_ptr<int>>
など簡易なリソースを確保後、わざと loadTexture("enemy.dat")
を呼び例外を発生させる。catch
でメッセージ表示。終了時に確保済み資源が自動解放されることを確認。目的: 独自例外で入力不正を表現。
class InvalidParam : public runtime_error を作る。Enemy のコンストラクタで hp < 0 または
name.empty() なら InvalidParam を投げる。try
範囲で複数回生成を試し、catch(const InvalidParam&)
で個別メッセージを表示。目的: 伝播と再スローのパターンを体験。
low() → mid() → main()
の三層構造。low() で runtime_error
を投げ、mid() でログだけ出して throw;。main()
で最終捕捉し、ユーザー向けメッセージを表示。提出物:ソース一式と実行ログ。どこで何を捕捉し、どのように再スローしたかをコメントで説明すること。
catch は具体 →
抽象。catch(...) は最後の保険。new/delete
を直接使わない設計を基本に。what()
を有益にする。