【C++】optional – 値が「ある」/「ない」を安全に表現する
はじめに
C++でコードを書いていると、値を返せない場合というのがままあります。従来、このような場合は負の値やnullptrなど、通常出てこないであろう値を返すことで表現していました。しかし、これらの値が意味を持つ場合も考えられます。このような場合に、値がないことを安全に返すクラスがC++17で追加されました。それが本エントリで紹介するstd::optionalです。
本エントリでは、このstd::optionalを使い方を中心に紹介したいと思います。
定義
std::optional<T>は、次のどちらかの状態を持つクラステンプレートです。
- 型
Tの値を持っている。 - 値を持っていない。
利用するには<optional>をインクルードします。
#include <optional>
std::optional<int> number = 42; // 値あり
std::optional<int> empty; // 値なし
std::optional<int> missing = std::nullopt; // 値なしを明示std::nulloptは「値なし」を表す定数です。これを使うことで、値の「ある」「なし」を区別できます。
使い方
値の有無を確認して取り出す
ifで値の有無を調べてから*で値を取り出します。
#include <iostream>
#include <optional>
#include <string>
int main()
{
std::optional<int> count = 0;
if(count) {
std::cout << "count: " << *count << '\n';
}
std::optional<std::string> name = "Alice";
if(name.has_value()) {
std::cout << "name: " << *name << '\n';
std::cout << "length: " << name->size() << '\n';
}
}if(count)で見ているのは、格納された数値が真かどうかではなく、値が存在するかどうかです。has_value()でも同じ判定ができます。クラスのメンバーには->でアクセスできます。
値の取り出し方
値の取り出し方は、値がなかった場合にどのように扱うかで変わってきます。
| 書き方 | 用途 | 値がない場合 |
|---|---|---|
*opt、opt->member | 存在を確認した値にアクセスする | C++17では未定義動作 |
opt.value() | 値なしを例外として扱う | std::bad_optional_accessを送出 |
opt.value_or(default_value) | 代わりの値を使う | default_valueを返す |
#include <iostream>
#include <optional>
int main()
{
std::optional<int> timeout;
// 未設定なら30を使う。
int seconds = timeout.value_or(30);
std::cout << "timeout: " << seconds << '\n';
try {
std::cout << timeout.value() << '\n';
} catch (const std::bad_optional_access&) {
std::cout << "timeout is not set\n";
}
}value_or()は参照ではなく値を返します。元のoptionalは更新されません。
値を設定・削除する
値の設定には代入またはemplace()、値の削除にはreset()を使います。削除にはstd::nulloptを代入しても構いません。
#include <iostream>
#include <optional>
#include <string>
int main()
{
std::optional<std::string> message;
message = "Hello";
std::cout << *message << '\n'; // Hello
// std::string(5, '!')を直接構築する
// すでに値がある場合は、その値を破棄してから構築する
message.emplace(5, '!');
std::cout << *message << '\n'; // !!!!!
message.reset();
std::cout << std::boolalpha << message.has_value() << '\n'; // false
message = "Again";
message = std::nullopt; // これも値なしに戻す操作
}使用例
例1: 「見つからないかもしれない」検索処理
std::optionalがもっとも活きる典型例は、失敗する可能性のある検索処理です。
#include <optional>
#include <vector>
#include <string>
#include <iostream>
struct User {
int id;
std::string name;
};
std::optional<User> find_user(const std::vector<User>& users, int id)
{
for(const auto& user : users) {
if(user.id == id) {
return user; // 見つかった場合はUserを返す
}
}
return std::nullopt; // 見つからない場合
}
int main()
{
std::vector<User> users = {{1, "Alice"}, {2, "Bob"}};
if(auto result = find_user(users, 2)) {
std::cout << "見つかりました: " << result->name << "\n";
}
else {
std::cout << "ユーザーが見つかりません\n";
}
// 見つからない場合の例
auto missing = find_user(users, 99);
std::cout << "id=99のユーザー名: "
<< missing.value_or("不明") << "\n";
}例2: 文字列から数値への安全な変換
#include <optional>
#include <string>
#include <charconv>
#include <iostream>
std::optional<int> parse_int(const std::string& str)
{
int value{};
auto result = std::from_chars(str.data(), str.data() + str.size(), value);
if(result.ec == std::errc()) {
return value;
}
return std::nullopt; // 変換失敗
}
int main()
{
for(const auto& text : {"123", "abc", "45x"}) {
if(auto num = parse_int(text)) {
std::cout << text << " -> " << *num << "\n";
}
else {
std::cout << text << " -> 変換失敗\n";
}
}
}例3: 関数の任意引数(デフォルト値の代わり)
#include <optional>
#include <iostream>
void connect(const std::string& host, std::optional<int> port = std::nullopt)
{
int actual_port = port.value_or(8080); // デフォルトポート
std::cout << host << ":" << actual_port << " に接続します\n";
}
int main()
{
connect("example.com"); // ポート省略 -> デフォルト値を使用
connect("example.com", 443); // ポート指定あり
}注意点
std::optionalを使う際は、いくつか気をつけたい点があります。
参照は保持できない
std::optional<T&>は標準では許可されていません(C++26で追加予定です)。参照が必要な場合はstd::reference_wrapperを組み合わせるなどの工夫が必要です。
空のoptionalをそのまま参照しない
値がない状態で使うと未定義動作になるため、has_value()やif(opt)での確認を習慣づけましょう。
「値がない」と「エラーの理由」は別物
std::optionalは「値があるかないか」しか表現できません。呼び出し側が失敗理由ごとに対応する必要があるなら、エラー情報を持つ独自の結果型などを検討してください。「存在しない」という情報だけで十分かどうかが、採用時の判断基準です。
まとめ
std::optional<T>は、「値がある」「値がない」を型で表現するための仕組みです。検索結果や省略可能な設定など、値の不在を自然に扱いたい場面で役立ちます。
- 値なしを返すときは
std::nulloptを使う。 if(opt)またはhas_value()で確認し、*や->でアクセスする。- 既定値を使うなら
value_or()、空を例外として扱うならvalue()を使う。 - 値を消すときは
reset()またはstd::nulloptの代入を使う。
まずは「見つからなければ特別な値を返す関数」を、std::optionalで表せないか考えてみてください。戻り値の意味が明確になり、呼び出し側でも値がない場合の処理を書きやすくなります。


ディスカッション
コメント一覧
まだ、コメントがありません