🛀
安全に未初期化をケアするstd::optional
コードレビューで以下のようなコードを見かけました。
double x = 0.0;
if (condition)
{
const int ret = doSomething1();
if (ret == 0)
{
x = 10.0;
}
}
if (x == 0.0)
{
doSomething2(x);
}
気になった個所だけ抜粋してますので良く分からないコードになっていることは一旦置いておきましょう。
float
やdouble
型の変数は等価演算子で比べるのはお勧めされていですが、これは 0.0 と比較したいのではなく、どちらかというとx
がまだ初期値のままかをチェックしています。でもコンパイラーの設定によって、0.0 との比較で警告がでますし、チェックの意図も分かりづらいです。
こういう場合は、私の一押しのstd::optional
を使うといいです。
#include <optional>
std::optional<double> x(std::nullopt);
if (condition)
{
const int ret = doSomething1();
if (ret == 0)
{
x = 10.0;
}
}
if (x.has_value())
{
doSomething2(x.value());
}
std::optional
はどの型の値も保存できるテンプレートクラスになっていて、デフォルト値がstd::nullopt
という特別な値になっています。
上記の例では紹介のためわざわざstd::nullopt
で初期化していますが、本当はその必要がないです。
std::optional
型の変数は、value()
という関数で値にアクセスできますが、その前に初期値のままか値が設定されているかは、has_value()
という関数でチェックできます。
またstd::optional
クラスはbool
へのキャスト演算子operator bool()
も定義されているため、if
文の条件としてそのまま書くこともできます。(if文は明示的なキャストになります)
これで上記のコードをもう少し簡単に書けます。
#include <optional>
std::optional<double> x;
if (condition)
{
const int ret = doSomething1();
if (ret == 0)
{
x = 10.0;
}
}
if (x)
{
doSomething2(x.value());
}
これだと初期化のままかをチェックしているって一目で分かりますし、double
の値を等価演算子で比較せずに済みます。
このコードをさらにリファクタリングしたい場合は、以下のようにラムダ式関数が使えます。
#include <optional>
auto get_x
( [] () -> std::optional<double>
{
if (condition && doSomething1() == 0)
return 10.0;
return {};
}
);
const auto x = get_x();
if (x)
{
doSomething2(x.value());
}
こうすると、x
を一行で設定できて、さらにconst
が付けられるようになりますね。
Discussion