導入
C++17で導入されたstd::variantは、複数の型を保持できる強力なコンテナですが、デフォルトでは「最初の型」が初期化された状態で生成されます。しかし、開発中に「まだ何も値がセットされていない状態」を表現したいケースは多々あります。そんな時、std::variantに初期値を持たせないような挙動を実現するために欠かせないのがstd::monostateです。これを使うことで、未定義状態によるバグを未然に防ぐことができます。
基礎知識
std::monostateは、<variant>ヘッダで定義されている「値を持たない単一の状態」を表すダミー型です。std::variant<T1, T2>のように定義すると、必ずT1かT2のどちらかで初期化されてしまいますが、std::variant<std::monostate, T1, T2>とすることで、意図的に「何も保持していない状態」を値として扱えるようになります。
実装/解決策
std::monostateをstd::variantの最初の型として指定するのが一般的です。これにより、デフォルトコンストラクタが呼ばれた際に、variantはstd::monostateとして初期化されます。値がセットされているかどうかを確認するには、std::holds_alternativeやstd::get_ifを使用します。
サンプルプログラム
以下のコードは、std::monostateを使ってvariantの状態を管理する例です。
include <iostream>
include <variant>
int main() {
// std::monostateを先頭に置くことで、「空」の状態を表現可能にする
std::variant<std::monostate, int, std::string> data;
// 初期状態はstd::monostate
if (std::holds_alternative<std::monostate>(data)) {
std::cout << “現在は何も値が保持されていません。” << std::endl;
}
// 値を代入
data = 100;
// 値の確認
if (auto val = std::get_if<int>(&data)) {
std::cout << “保持している値: ” << val << std::endl;
}
// 再び空の状態に戻すことも可能
data = std::monostate{};
return 0;
}
応用・注意点
実務での注意点として、std::monostateが含まれるvariantに対してstd::get<T>を直接呼ぶと、もし現在の状態がstd::monostateだった場合にstd::bad_variant_access例外がスローされます。アクセスする際は、必ずstd::get_ifによるポインタチェックを行うか、std::visitを使用して全ての型(monostateを含む)に対する処理を網羅するようにしましょう。また、std::monostate自体にはデータが含まれないため、メモリサイズはvariantのオーバーヘッド分のみとなり、非常に軽量です。積極的に活用して、安全な状態管理を心がけてください。

コメント