📚

strong strong type

に公開

以前の記事で言及した関数の引数としてbool変数を使うのはあんまり良くないという話についてなんですが

本当はboolだけの話ではなくて、ちゃんと意味のある引数だったら、intfloatboolみたいな一般的な型を使ってはいけないと思います。例えば、ユーザのIDだったら、ただのintでもunsigned intでも駄目ということです。以下のような関数をコールする時、

void createUser(unsigned int id, unsigned int age, int level)
{
    // ...
}

createUser(0, 30, 99);

コール側からすると分かりづらいし、渡す引数の順番が間違っていても普通にビルドできてしまいます。先の記事のようにstrong typeが必要です。ここもenum classを使ってもいいのですが、enum classは値を全部定義の時に宣言しないといけないため、こういうどんどん増やしていきたい型には向いていないです。

今回はどのユースケースでも使える方法を紹介します。

struct UserID
{
public:
    UserID(int id)
    : m_id(id)
    {}
    
    explicit operator int() const
    {
        return m_id;
    }

private:
    int m_id;
};

こうすると、UserIDはこのコンストラクタを使わない限り作れない上に、明示的にキャストしないとただのintに変換できないため、間違って違う変数を指定したりはできなくなるはずです。他に出力用の<<演算子や、比較用の==演算子、ソート用の<演算子のオーバーロードを追加すれば、もっと使いやすくなります。

同様にUserAgeUserLevelという型も定義したいのですが、毎回こういう構造体を手動で定義するのは手間ですよね。そこで以下のように名前を指定するだけで、上記の構造体を作ってくれるマクロを定義すれば、一行で定義できるようになります。

#define STRONG_TYPE(Name)           \
struct Name                         \
{                                   \
public:                             \
    Name(int value)                 \
    : m_value(value)                \
    {}                              \
                                    \
    explicit operator int() const   \
    {                               \
        return m_value;             \
    }                               \
                                    \
private:                            \
    int m_value;                    \
};

STRONG_TYPE(UserID);
STRONG_TYPE(UserAge);
STRONG_TYPE(UserLevel);

これで、コール側が以下のようになります。

void createUser(UserID id, UserAge age, UserLevel level)
{
    // ...
}

createUser(UserID(0), UserAge(30), UserLevel(99));

引数の順番を間違うとちゃんとコンパイルエラーになります。

全引数のために型を作るのはちょっとやりすぎかもしれないですが、IDのような大事な型だったら採用した方がいいと思います🙋


|cpp記事一覧へのリンク|

Discussion