ここでは「クラスと継承の問題」とありますが、ここでは継承によるわかりづらい問題や発生するバグなどを挙げたいと思います。
目次
virtual
比較的わかりやすい問題は、継承元・継承クラスのデストラクタにvirtual
を入れていない場合に起こるバグです。
この問題は書き方に気をつければ問題ないのですが、オブジェクト指向に慣れていない人や、計画性を持たずにクラスライブラリを製作していると起こりやすいと思います。
また、継承元と違う動作をする関数にも継承元クラスに関数にvirtual
をつけないと正しく動作しない場合があります。
他には、C++言語の大きな特徴の「多重継承」による問題もあります。
例えば、以下のような構成の場合、
[code=”c++”]
// 利便上、structで構成しています
struct A {int a;}
struct B : public A {int b;}
struct C : public B {int c;}
struct D : public B, public C {int d;}
[/code]
クラスD
は、クラスA
を継承するクラスB
、C
を多重継承しています。
このとき、クラスD
からクラスA
のメンバにアクセスする場合、B::a
、C::a
のように指定しなければなりませんが、B::a
で値を書き換えたとき、C::a
でアクセスしたときは書き換えられていない問題が発生します。
これは、クラスB
、C
それぞれが単独でクラスA
を所有しているためです。
この時、それぞれのクラスA
を共有させたい場合、以下のようにします。
[code=”c++”]
struct A {int a;}
struct B : virtual public A {int b;}
struct C : virtual public B {int c;}
struct D : public B, public C {int d;}
[/code]
このように継承するクラスにvirtual
を付けることにより、クラスA
は共有されます。
なぜこのような問題があるのかというと、なにより「多重継承」と言う特有な機能のため、より多様性に構築を行えるようにするためだろう、と私は思います。
virtual
は他のキーワードよりも使うところが多くあるので、混乱の元になる場合もありますが、使い方さえ理解できればそれほど問題は起きないと思います。
メンバ変数の位置
普段あまり気にすることが無いと思われるメンバ変数ですが、稀に問題が発生する場合があります。
[code=”c++”]
// 簡略化して書いています
template
class callback{
public:
typedef void (T::*Func)();
callback(Func f,T* c):_f(f),_c(c){}
void operator()(){(_c->*_f)();}
void chenge(T* c){_c = c;}
private:
Func _f;
T* _c;
};
[/code]
あまり使わない記法(_c->*_f)();
(メンバ関数ポインタ_f
とクラスポインタ_c
を使用したメンバ関数呼び出し)がありますが、これを利用したクラス(遅延呼び出しとか)を作成する場合、途中でクラスポインタを変更する処理(chenge(T* c);
)が正しく行われない場合があります。
この場合は、メンバ関数ポインタをクラスポインタの後におくと解消されるようです。
もっとも、書き方によって稀に起こる問題なので、コンパイラによっては起こらないかもしれないし、他の事例では入れ替えるだけで解決するかどうかはわかりませんけど。