第61回 std::optional
「値があるかもしれないし、ないかもしれない」を型として表現する C++17 の標準ライブラリ。ポインタの nullptr、特殊値(-1 など)、例外に頼らない明示的な null 表現。
このページで押さえること
✅ 最低限
std::optional<T> ― 値を「持つ/持たない」
opt.has_value() / if (opt) で存在確認
opt.value() / *opt で中身取得
- 無い値は
std::nullopt
⭐ 余裕があれば
value_or(default) ― 無ければデフォルト
- C++23 の monadic operations(
and_then など)
- optional<reference> は不可(T& を包めない)
- 戻り値としての活用
1. 何の問題を解決するか
C の流儀曖昧
// 見つからないとき -1? 他の特殊値?
int find_index(const Data& d) {
if (/* 見つかった */) return idx;
return -1; // 規約?
}
// ポインタで null 返却?
User* find_user(int id) {
if (!found) return nullptr;
// 所有権は? delete する?
return ...;
}
optional明示
#include <optional>
std::optional<size_t> find_index(const Data& d) {
if (/* 見つかった */) return idx;
return std::nullopt;
}
auto idx = find_index(d);
if (idx) {
// 見つかった
use(*idx);
}
2. 基本操作
主要 APIoptional
std::optional<int> a; // 空
std::optional<int> b = 42; // 値あり
std::optional<int> c = std::nullopt; // 明示的に空
// 存在確認
if (b.has_value()) { ... }
if (b) { ... } // bool 変換
// 取り出し
int x = *b; // 空だと UB
int y = b.value(); // 空だと std::bad_optional_access 例外
// 空ならデフォルト値
int z = a.value_or(0); // a が空なら 0
// 書き換え
a = 10; // 値を入れる
a = std::nullopt; // 空にする
a.reset(); // 空にする(同じ)
空のとき * や value() に注意: *opt は空でも実行できてしまい未定義動作。必ず if (opt) 等で確認してから使うか、value()(例外版)を使う。
3. 典型的な使い所
- 関数の戻り値:見つからないかもしれない検索関数
- パース結果:
parse_int(string) -> optional<int>(失敗時 nullopt)
- 遅延初期化:まだ値が決まっていないメンバ
- Null 許容フィールド:名前と違って「任意項目」を表現
parse_int典型
std::optional<int> parse_int(std::string_view s) {
try {
return std::stoi(std::string{s});
} catch (...) {
return std::nullopt;
}
}
if (auto n = parse_int("42")) {
std::cout << *n; // 42
}
4. ポインタ/参照との違い
- ポインタ
T*:null が表現できるが所有権が曖昧、寿命も不明
- 参照
T&:null 表現不可、必ず有効なオブジェクトを指す
std::optional<T>:値として保持、所有権も寿命も明快
optional は T の実体を値として包む(ポインタではない)。だからスコープで自動破棄され、リーク懸念なし。ただし T の参照は optional に入れられない(optional<T&> は未対応)。代わりに T* を包む、または std::reference_wrapper<T> を使う。
確認クイズ
Q1. 空の optional を表す定数は?
std::nullptr
std::null
std::nullopt
0
std::nullopt が optional 用の「値なし」。
Q2. optional が空のとき *opt はどうなる?
例外
未定義動作
0 が返る
nullptr
*opt / opt->member は空でも UB。例外が欲しいなら opt.value()。
Q3. 空なら既定値を使う書き方は?
opt.default(0)
opt.value_or(0)
opt ? *opt : 0
opt.or(0)
②が答えだが、③も実質同等。value_or の方が簡潔でイディオム。
Q4. optional<T&> は使える?
使える
使えない(T* や reference_wrapper で代替)
C++20 以降のみ可
使えるが警告が出る
optional は参照型を直接は保持できません。T* か std::reference_wrapper<T> を中に入れます。