null許容型って便利だけど、C++にはないよね?
ってことで、C#の実装を参考にテンプレートを使って実装してみました。
スマートポインタでよくね?
-> いや、ほらnull許容型のほうが用途がわかりやすいとか・・ね・・・
単にテンプレートとか、演算子のオーバーライドとかの理解を確かめるために作っただけです。
ソースコードは以下においてます。
nullable.hpp
参考サイト:
Null 許容型 (C# プログラミング ガイド)
何がうれしいの?
今まで
typedef struct{
double value;
bool has_value;
] nullable_double_t;
nullable_double_t nd = {};
// なんか処理(この間にndに値を代入したり)
if(nd.has_value) {
// 値が設定されていたら実行する処理
}
ってのが、↓のようにかけるようになる。
#include <nullable.hpp.
nullable<double> dn;
// なんか処理(この間にndに値を代入したり)
if(nd) {
// 値が設定されていたら実行する処理
}
って書けたらいい感じだよね?ってのが実装した動機です。
動作環境
タイトル通りC++11以降。
(たぶんOKなはず。記載されたコードの動作チェックはrepl.itで確認しました)
使い方
nullable.hppをincludeして変数宣言。初期値はnullなので、条件式に放り込むと、falseになる。
値を使用するときはget_value()
を使う。
#include "nullable.hpp"
using namespace std;
using namespace totoho;
nullable<int> ni1;
if(ni1) {
cout << "ni1 is " << ni1.get_value() << endl;
} else {
cout << "ni1 is null..." << endl;
}
// ni1 is null...
宣言時に値を指定することも可能。
nullable<double> nd1(0.5f);
if(nd1) {
cout << "nd1 is " << nd1.get_value() << endl;
} else {
cout << "nd1 is null..." << endl;
}
// nd1 is 0.5
nullptrを代入することで初期化できる。
nd1 = nullptr;
if(nd1) {
cout << "nd1 is " << nd1.get_value() << endl;
} else {
cout << "nd1 is null..." << endl;
}
// nd1 is null...
get_value()
を使わずにキャストすることも可能。
nd1 = 2.1f;
if(nd1) {
cout << "nd1 is " << (double)nd1 << endl;
}
// nd1 is 2.1
nullの状態で値を使用するとruntime_errorが投げられる
nullable<double> nd2;
// cout << "nd2 is " << nd2.get_value() << endl; // runtime_error
実装について
基本的には構造体の例と同じく、型Tの変数とnullを示すためのフラグの2つのメンバ変数を持っています。
template <typename T>
class nullable
{
private:
bool has_value_;
T value_;
...
コンストラクタは、デフォルトではhas_value_
にfalse
を設定するため、デフォルトコンストラクタを定義。
nullable<T>(void)
: has_value_(false)
{}
あとは、T
型とnullable<T>
型をそれぞれ引数にとれるように以下を定義。
(追記)explicit
指定子はnullable<T> nd = 0.5;
みたいに初期化時にT
型を設定できるように削除。
nullable<T>(T val)
: has_value_(true)
,value_(val)
{}
nullable<T>(const nullable<T> &rhs)
: has_value_(rhs.has_value_)
,value_(rhs.value_)
{}
あとは2つのメンバ変数をset, getするI/Fと便利にするための演算子のオーバーライドが続くので、それぞれ説明。
null値のsetter, getter
nd1 = nullptr
みたいに、nullptr
を使用してhas_value
フラグをfalse
に設定できるようにvoid set_null(void)
とvoid*
型を引数にとる=
オペレーターのオーバーライド。
void set_null(void)
{
has_value_ = false;
}
...
nullable<T> &operator=(const nullptr_t rhs)
{
has_value_ = false;
return *this;
}
if(nd1)
みたいにif文なんかの条件式で直接使用できるように、has_valueのgetterとしてbool has_value(void)
と、boolをオーバーライド。explicit
をつけているけどboolのオーバーライドはこことかにあるように、if文などの条件式では暗黙的に変換されるらしい(ややこしい)。
bool has_value(void) const
{
return has_value_;
}
...
explicit operator bool(void) const
{
return (*this).has_value();
}
T型, nullable型のsetter, getter
T
型、nullable<T>
型を代入できるようにset_value
I/Fと=
オペレータのオーバーライド。T
型代入時はhas_value_
にtrue
を自動的に設定。
// T型代入用
void set_value(const T &rhs)
{
value_ = rhs;
has_value_ = true;
}
...
nullable<T> &operator=(const T &rhs)
{
set_value(rhs);
return *this;
}
//nullable<T>型代入用
nullable<T> &operator=(const nullable<T> &rhs)
{
set_value(rhs);
return *this;
}
...
void set_value(const nullable<T> &rhs)
{
value_ = rhs.value_;
has_value_ = rhs.has_value_;
}
最後に値を取り出すためのI/Fとしてget_value
とキャストして値を取り出せるように戻り値T
型の単項演算子をオーバーライド。
get_value
ではhas_value
がfalse
なら例外をスロー。
T get_value(void) const
{
if (!has_value_)
{
throw std::runtime_error("value is null!");
}
return value_;
}
...
explicit operator T(void) const
{
return (*this).get_value();
}
今後の課題
演算子のオーバーライド周りが勉強不足なので、↓の書き方ができない。
どう書けばいいんだろ?
コンストラクタのexplicit
外すことで行けるそうです。
コードなおしてます。
nullable<double> nd3 = 0.5f;
さいごに
初投稿です。この業界も3年目で、やっぱりアウトプットを出す習慣をつけないとなぁということで、四半期に1本のペースでは記事を書けることを目標に頑張っていきます。