2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C++11でC#っぽいnull許容型を実装してみた

Last updated at Posted at 2018-07-01

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つのメンバ変数を持っています。

nullable.hpp
template <typename T>
class nullable
{
private:
  bool has_value_;
  T value_;
  ...

コンストラクタは、デフォルトではhas_value_falseを設定するため、デフォルトコンストラクタを定義。

nullable.hpp
nullable<T>(void)
: has_value_(false)
{}

あとは、T型とnullable<T>型をそれぞれ引数にとれるように以下を定義。
(追記)explicit指定子はnullable<T> nd = 0.5;みたいに初期化時にT型を設定できるように削除。

nullable.hpp
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*型を引数にとる=オペレーターのオーバーライド。

nullable.hpp
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文などの条件式では暗黙的に変換されるらしい(ややこしい)。

nullable.hpp
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_valueI/Fと=オペレータのオーバーライド。T型代入時はhas_value_trueを自動的に設定。

nullable.hpp
// T型代入用
void set_value(const T &rhs)
{
  value_ = rhs;
  has_value_ = true;
}
...
nullable<T> &operator=(const T &rhs)
{
  set_value(rhs);
  return *this;
}
nullable.hpp
//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_valuefalseなら例外をスロー。

nullable.hpp
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本のペースでは記事を書けることを目標に頑張っていきます。

2
3
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?