C/C++ - 言語仕様編 第9回 〜クラス - 後編 - 〜

オペレータ・オーバーロード

メソッドがオーバーロードできるのと同様に、C++では、クラスごとにその演算子の挙動を再定義することができる。 オーバーロードできるオペレータは多岐にわたり、大きく分けて、 関数オペレータ・変換オペレータ・参照オペレータ・代入オペレータ・ 単項オペレータ・算術オペレータ・比較オペレータなどが挙げられる。

いずれにせよ、T型オブジェクトを返し、A型オブジェクトを引数とするオペレータ # は、 T operator # (const A& a); などとして宣言する。

代入オペレータ群

= を基本として、*=、/=、%=、+=、-=、<<=、>>=、&=、^=、|= をまとめて、代入オペレータという。 クラスCでこれらの代入オペレータ # を定義する際には、

const C& operator # (const C& c);

として宣言する。const C&型を返すが、これは、*thisを返す。すなわち、演算の結果、 オペレータの左辺のインスタンスは書き換わることとなる。代入オペレータ群は、 数あるオペレータの中で、最も優先度が低く、他のオペレータの演算がすべて終了した後に、このオペレータが演算される。

算術オペレータ群

*、/、%、+、-、<<、>>、&、^、| をまとめて、二項算術オペレータという。クラスCでこれらの 二項算術オペレータ # を定義する際には、

const C operator # (const C& c)const;

として宣言する。const C型を返すことと、これ自身がconstメソッドであることから分かるように、 オペレータの左辺は書き変わらず、新しくオブジェクトを構築して、これを返す。

比較オペレータ群

<, <=, >, >=, ==, !=や、&&, ||をまとめて、二項比較オペレータという。クラスCでこれらの 二項比較オペレータ # を定義する際には、

bool operator # (const C& c)const;

として宣言する。自身の値が書き換わらないのは当然のこと、返り値がbool型変数であることから分かるように、 trueかfalseをその結果として返す。

算術オペレータと比較オペレータの優先順位は、基本的に、算術オペレータの方が比較オペレータより上であるが、 特に、算術オペレータのうち、論理オペレータである、&,^,|は、比較オペレータより優先度が低い。 さらに、&&や||は、この論理オペレータに比べてもさらに優先度が低く設定されている。

単項オペレータ群

++, --や、 ~, !, +, -などが、単項オペレータである。単項であるため、引数が必要なく、 後者4つに関しては、余程の事情がない限り、

T operator #(void)const;

として宣言することとなる。優先順位は、これまで挙げたオペレータよりもさらに高く設定されているため、 まず、先にこのオペレータが実行されることとなる。

一方、前者の2つのオペレータ、インクリメントとデクリメントは、前置きと後置きの2種があり、 前置きのオペレータは、

C& operator ++(void);

などと定義され、後置きのオペレータは、

C operator ++(int);

などと定義される。後置きのオペレータはかなり特殊で、まず、引数のintはダミーの引数であり、 実際には使用されない。また、前置きオペレータがC&を返すのと異なり、後置きはCを返す。 つまり、前置きはインクリメント後の*thisを返すが、後置きは、インクリメント前の値を返すこととなる。

後置きインクリメント・デクリメントオペレータの優先度は、全オペレータの中でもかなり高く設定されている。

参照オペレータ群

->, [], &, * が該当し、->と&は T*型オブジェクト、[]と*は、T&型オブジェクトが返るものとして通常定義される。 []は、引数と1つ取り、

T& operator [] (const S& index);
const T& operator [] (const S& index) const;

などとして定義する。残りは全て単項オペレータである。これら全てに共通することであるが、 ポインタに関わる操作であり、特に、&のオーバーロードは、ポインタの取得という操作が上書きされてしまうため、 注意が必要となる。ポインタ変数であるか参照変数であるかという条件が課せられるという理由もあり、 通常、余程の事情がない限り、通常の意味でのオペレータと同じ意味で定義するのが無難である。

->と[]の優先度は、後置きインクリメントよりもさらに高く、&と*は、単項オペレータより低めに定められる。

変換オペレータ

平たく言えばクラスCからのキャストを定義するものである。形式としてコンストラクトキャストの形式と、 C言語スタイルキャストの2つがあり、前者は->オペレータ以上の優先度を持ち、 後者は、単項オペレータより低く、二項演算オペレータより高い優先度を持つ。

いずれの場合にせよ、S型へのキャストは、

operator S(void)const;

として宣言される。返り値の型はSで明らかであるため省略される。

関数オペレータ

全オペレータの中で、最も高い優先度を持つオペレータであり、とりあえず、まず、呼び出される。 他のオペレータと全く異なり、返り値の型の制約がないことはもちろんのこと、 引数の型はおろか、その数すらも自由であり、当然オーバーロード可能である。

T operator()(A a, B b, C c, ... );

などと定義すればよい。

継承

あるクラス D に対して、

class C : public D{
public:

};

としてクラスCを宣言すると、クラスCは、クラスDのプロパティやメソッドを持ち、クラスDの特徴を有しながら、 その特徴をより特殊化したクラスとなる。

これが、OOPにおける継承と呼ばれる概念であり、このとき、クラスCは、クラスDを継承すると表現する。 また、DはCの基底クラス、CはDの派生クラスという言い方をしたり、 単純に、DをCの継承元、CをDの継承先と呼称する事もある。

基底コンストラクタ

クラスCのコンストラクタは、初期化の段階で、

C::C(void):D( ... ){}

として、基底クラスDのコンストラクタを呼ぶことで、クラスDは初期化される。

アクセス指示子

最初にも書いたとおり、クラスのプロパティは、外から見えない、private なプロパティである。 public: と同様に、private: と明示的に書くことも出来るが、省略時は自動的にprivate: となる。

CがDを継承したとき、Cは、Dのprivateなプロパティを見る事は出来ない。 もし、外に公開はしたくないが、継承先に対してのみ公開を許す場合は、 protected: と指定する。クラスの外、すなわちオブジェクトからはそのプロパティにアクセスできないが、 継承先からはそのプロパティを見ることが出来る。逆に、継承することによって、 外部から見えないプロパティを見ることが出来るようになる。

また、継承する場合の public: も意味合いは同じであり、publicなプロパティをpublicに継承する。 これを、protectedとすると、publicなプロパティ・メソッドの全てがprotectedとなり、privateとすれば、 全てのプロパティとメソッドがprivateとなる。省略時にはprivateであるため、

class C : D{
...
};

などとして継承すると、Dのプロパティやメソッドはおろか、コンストラクタすらもprivateで継承してしまうため、 事実上、CからDへのキャストが不可能となる。余程の事情がない限り、継承はpublicで行う。

仮想継承

クラスDからクラスCへの仮想継承は、

class C : public virtual D{
...
};

のように定義される。仮想メソッドの場合と同じく、Dのプロパティやメソッドは、 クラスCのインスタンスをオブジェクトとして参照した瞬間に定められる。

ダイナミックキャスト

クラスCが仮想クラスDを継承するとき、D*型オブジェクト d からC*型オブジェクトへの変換は、

dynamic_cast<C*>(d);

と書き、これをダイナミックキャストと呼ぶ。同様に、D&型オブジェクト d からC&型オブジェクトへの変換も

dynamic_cast<C&>(d);

と書き、これもダイナミックキャストと呼ぶ。

基底クラスへのキャストは常に成功し、これをアップキャストと呼ぶ。一方、派生クラスへのキャストは

D* d=new C(t);
D& d=C(t);

のように、初期化されている場合に成功し、これをダウンキャストと呼ぶ。

キャストが定義されていない場合、スタティックキャストの場合はコンパイルエラーが起こり、 リインタプレットキャストの場合はエラーを検出しない一方、ダイナミックキャストは、 実行時に成功するか失敗するかを判別する。

多態性とオーバーライド

オーバーライド

仮想メソッドT m(A1 a,B1 b); を持つ仮想クラスDを継承したクラスCに対して、

class C : public D{
public:	
	T m(A1 a, B1 b);
};

と、引数・返り値の型が完全に一致するメソッドを再定義することをオーバーライドという。

クラスCでオーバーライドしたメソッドmもまた仮想メソッドであり、自動的にクラスCは仮想クラスである。

なお、継承元で定義される仮想関数と異なる引数・返り値の型で定義したものは、 たとえ同じ名前であったとしても、オーバーライドとはならず、ただのオーバーロードとなる。

純粋仮想メソッドと純粋仮想クラス

仮想メソッドの考え方を押し進めると、さらに、クラスの定義時点でメソッドの振る舞いを定めないことも許され、

virtual T m(A a, B b)=0;

などと宣言することができる。このmを、純粋仮想メソッドと呼ぶ。

この純粋仮想メソッドを1つでも含むクラスを、純粋仮想クラスと呼ぶ。

特に、すべてのメソッドが純粋仮想メソッドであり、かつ、 仮想デストラクタと静的プロパティのみしか持たないクラスをインターフェースと呼ぶ。

仮想クラスと異なり、純粋仮想クラスは、完全に定義されていないために、 インスタンス化することが出来ない。

インターフェースの実装

純粋仮想メソッド virtual T m(A a, B b)=0; が宣言されている、インターフェース I に対して、

class C : public I{
public:
	T m(A a, B b);
};

T C::T(A a, B b){...}

として、純粋仮想メソッドをオーバーライドすることで定義が行われ、インターフェースIはクラスCによって実装される。

仮想デストラクタ

インターフェースIと、それを実装するクラスCに対して、I*型オブジェクト p は、C型インスタンスを参照することが 可能であるが、このとき、p を delete で解体する場合、 Iのデストラクタではなく、Cのデストラクタを呼ばなくてはならない。

virtual ~C(void); として宣言された仮想デストラクタは、クラスをインスタンス化して、 それをオブジェクトとして参照した瞬間に、どのデストラクタから呼び出すかを決定する。

従って、

I* p=new C;

とすると、デストラクタは、クラスCのものが選択されることになる。

このようにして多態性を実現しようとする場合は、常に仮想デストラクタとしなければならない。

多態性オブジェクト

インターフェースIとその実装であるクラスCに対して、

I* p=new C;

とすることで、*pはインターフェースIの提供するメソッドを呼び出す多態性オブジェクトとなる。

インターフェースIを、クラスC1,C2,C3がそれぞれ継承しているとき、

I* p[3];
p[0]=new C1;
p[1]=new C2;
p[2]=new C3;

などとすると、*p[0], *p[1], *p[2]は、それぞれインターフェースIの提供するメソッドを呼び出すオブジェクトとなる。

それぞれ、呼び出す事のできるメソッドの種類は共通であるが、その挙動は、継承したクラスの 実装によって変わることになる。 

多態性と参照変数

インターフェースIに対して、I& 型オブジェクトは、

I& p=c;

として、Iを継承するC型多態性オブジェクト c を受けることができる。

同様に、関数の I&型 引数は、多態性オブジェクトを受け取る事が出来て、 関数内で I& 型オブジェクトが呼び出すメソッドは、その参照インターフェースが持つ インスタンスによってその挙動が変わる。

インターフェースの多重継承

あるクラスDと、インターフェースI1, I2に対して、

class C : public D, public I1, public I2{
...
};

のように、あるクラスと複数のインターフェースを継承することを多重継承という。

多重継承を行うと、クラスCは、クラスDとインターフェース I1, I2 のすべてのメソッドとプロパティを 受け継ぐことになるが、例えば、I1とI2が共通のインターフェースIを継承すると、 Cは、I1由来のIと、I2由来のIで、同じ名前で区別できない複数のプロパティ・メソッドを所有することとなる。

この状態を菱形継承と呼び、コンパイルエラーとなる。これを防ぐ1つの方法は、Iからの継承の段階で、 I1とI2はIを仮想継承し、Iのプロパティ・メソッドをCのインスタンスをオブジェクトとして参照した瞬間に 定めるようにすることである。

クロスキャスト

クラスCが、直接、あるいは間接的な継承関係を持たないインターフェースI1とI2の多重継承であり、 I1*型オブジェクト p がCのインスタンスを参照しているとき、

dynamic_cast<I2*>(p);

とするI1*からI2*へのキャストを、クロスキャストと呼ぶ。クロスキャストの名は、ダウンキャスト+アップキャストを行う事に由来し、 多重継承+ダイナミックキャストによって実現される。


文章作成 : yukki-ts (-+-twilight serenade-+- [stage])