変換関数や演算子関数について

こんにちは、ぷないしんです。

 

昨日のカウンタークラスを作成するにあたってサラっと変換関数や演算子関数などとのたまっていましたが、今日はもう少し掘り下げていきたいと思います。

 

最初に昨日のヘッダーファイルを張っておきます

 

NewCounter.h

//カウンタクラス

#ifndef ___Class_Counter
#define ___Class_Counter

#include <limits>

class Counter{
    unsigned cnt;   //カウンタ

public:
    //コンストラクタ カウンタを0に
    Counter() : cnt(0){}

    //unsigned型へ変換する
    operator unsigned() const {
        return cnt;
        }

    bool operator!() const {
         return cnt == 0;
         }

    //前置増分演算子++
    Counter& operator++(){
         if (cnt < std::numeric_limits<unsigned>::max()) cnt++;
         return *this;
    }

    //後置増分演算子++
    Counter operator++(int){
        Counter x = *this;
        ++(*this);
        return x;
    }

    //前置減分演算子--
    Counter& operator--(){
        if (cnt > 0) cnt--;
        return *this;
    }

    //後置減分演算子--
    Counter operator--(int){
        Counter x = *this;
        --(*this);
        return x;
    }
};

#endif

 

 

まず変換関数とは

特定の型の値を生成して返すメンバ関数のことで、例えば"Type型への変換関数"とは下のような名前のメンバ関数と定義しています。

 

operator Type

(Type型への変換関数の関数名)

 

前回のカウンタクラスで使用していたのはunsigned型への変換関数で、関数名としては

"operator unsigned"になり、定義としては次のようにします。

 

operator unsigned() const { return cnt;}

 

関数名として"operator unsigned"は二つの単語を使い構成しています。

というのも関数名それ自体が返却値の型を表しているからで、返却値の型を指定することはできないし、引数を受け取る事もできません。

(変換関数は基本的にconstで実現する)

 

この変換関数"operator unsigned"を使えばCounterからunsignedへ型を変換する時、明示的にも暗黙的にもキャストできるというわけです。

ソースとしては次のような感じですね。

 

unsigned x;
Counter cnt;
~省略~
x = unsigned(cnt);
x = (unsigned)cnt;
x = static_cast<unsigned>(cnt);
x = cnt;

 

一番下は暗黙的キャストで上3つは明示的キャストです。

このどれもがcntのカウンタをunsigned型の整数値として取り出します。

 

実はこれ、組み込み型で行う型変換と同じ形ですね。

 

int i;
double a;
~省略~
a = double(i);
a = (double)i;
a = static_cast<double>(i);
a = i;

 

これは分かりやすいし使いやすい。

 

ちなみに、変換関数"operator unsigned"はCounterクラスのメンバ関数でもあるのでドット演算子を使って呼び出すことも可能です。

 

x = cnt.operator unsigned();

 

こんな感じですね

でも長くなるだけで特に便利でもないのでこの形で使うことは普通は無いでしょう。

 

まとめると、

オブジェクトをType型に変換する必要が多いなら、Type型へ変換するoperator Typeを変換関数として定義すると便利ということですね。

 

演算子関数

次に演算子関数についてですが、これも変換関数と同じで演算子関数の定義の仕方も簡単です。

例えば”💩演算子”は下の名前の関数として定義をします。

 

operator 💩

 

演算子関数"operator 💩"を定義するとクラス型オブジェクトにてその💩演算子使えるようになります。

 

Counterクラスで定義した三つの演算子(!、++、--)を見ていきましょう。

 

論理否定演算子!

初めに”!”演算子から見ていきます。

Counterクラスではカウンタの値が0かどうかを判定するという内容で定義します

 

関数名としては"operator !"で下の様に書きます。

 

bool operator! () const { return cnt == 0;}

("operator !"も二つの単語で構成されているがoperatorと"!"の間にスペースを入れても大丈夫!)

 

この関数はカウンタの値が0であればtrue、そうでなければfalseを返します。

ということはC++の標準である!演算子と同じ仕様になりましたね。

 

なのでこの様に利用できてしまいます。

 

if (!cnt) 動作

(cntが0であれば動作を実行)

 

これなら使い方をいちいち覚える必要ないので非常に便利ですね。

 

ポイントとしては演算子関数を定義するときは、その演算子の本来の仕様と出来る限り同じか類似した仕様になるように注意しましょう!

 

増分演算子、減分演算子

次に増分演算子(++)と減分演算子(--)ですが

クラスに対してこれらを定義する際は前置き後置きを区別しましょう。

下がよくある宣言形式です。

 

class C{
    ~省略~
public:
    Type operator++();
    Type operator++(int);
}

 

前置の時は引数を受け取らない形で、後置はint形の引数を受け取る形です。

また、各関数の返却値型Typeは任意なのですが、一応

前置は C&型

後置は C型

 

これで組み込み型で使う++と同じ仕様になります。

 

両者の違いとしては

・前置(++💩)は左辺値式(代入の左辺にも右辺にも置ける式)

・後置(💩++)は右辺値式(代入の右辺にしか置けない式)

 

Counterクラスの前置++演算子を定義するときはこんな感じです。

Counter& operator++(){
    if (cnt < std::numeric_limits<unsigned>::max()) cnt++;
    return *this;
}

 

インクリをした<自分>へ参照を返すために*thisによる返却をしています。

 

(クラスCの前置の増分/減分演算子はその呼び出しするときの式が左辺値式にするため、C&型の*thisを返すように定義する)

 

Counterクラスの後置++演算子を定義するときはこんな感じです。

Counter operator++(int){
    Counter x = *this;
    if (cnt < std::numeric_limits<unsigned>::max()) cnt++;
    return x;
}

 インクリをするの値を返す必要があるので、前置のときより手順が複雑です

 

①自分自身の*thisのコピーを作業用変数xに一時保存

②カウンタをインクリ

③関数から抜け出るとき、保存しておいたインクリ前のxを返却

 

と、こんな感じで後置きの場合はいったん*thisをコピーしておき、そのコピーを返却する流れですね。

 

(クラスCの後置の増分/減分演算子はインクリ/デクリ前の自身の値を返却するように定義しよう

 

また前置、後置を比較したところ

++、--の演算子関数は前置より後置の方がコスト的に高くなるかもしれないので、前置でも後置でもどちらでもよい場合は前置の方を使ったほうがいいですね。

 

ところで、この前置も後置も

if (cnt < std::numeric_limits<unsigned>::max()) cnt++;

のところは同じですね。

 

同じソースならこの部分を呼び出せば重複を解消できます。

だから上記のソースでは

    Counter operator++(int){
        Counter x = *this;
        ++(*this);
        return x;
    }

 と表すことが出来ますね。

 

最後に、これら定義した演算子関数をクラスのオブジェクトで適用することは、メンバ関数の演算子関数を呼び出していることと同義です、つまり下の様に解釈できます。

 

++x → x.operator++()

前置(引数無し)

 

x++ → x.operator++(0)

後置(ダミーの引数が渡される)

 

規定により、後置きの場合はダミーの値として0が渡されます。

 

また一応こいつらも変換関数と同じで下の様に表せますが読みにくくなるだけで使いませんね。

 

x.operator++()

(前置を呼び出す++xと同じ)

x.operator++(0)

(後置を呼び出すx++と同じ)

 

以上、変換関数と演算子関数でした。

 

これらを組み合わせて昨日のカウンタクラスを書いているのでぜひもう一度見てみてください。

 

ありがとうございました。

 

ぷないしん。