■二つのライブラリをまたぐシングルトン(っぽい)クラスでエラーが出る。
class HPLSequence;
class HPLCounter : public HPLSequence;;
というカウンタクラスがあって、HPLDXLibで定義されている。
HPLDXLibに下記クラスがある。
class HPLCommonStaticData;
class AbstractDXStaticData: public HPLCommonStaticData;
HPLAir**Libに下記クラスがある。(**は綴りが長いので**で。)
class HPLAir**StaticData : public AbstractDXStaticData;
class MinervaStaticData: public HPLAir**StaticData;
そんでもって、本編(例えばミナーヴァの冒険)では下記で定義。
class GStaticData : public MinervaStaticData;
extern GStaticData staticData;
で、この状態で実行すると、MinervaStaticDataのコンストラクタ辺りで、HPLCounter/HPLSequenceがNULLになったりするなどのエラーになる。
書き方やタイミングで変わることから、再現はほぼ100%ながらも、どこに初期化を入れるかで症状が変わって来るので、もっと違う所がおかしい。
当然HPLCounter/HPLSequence自体はポインタを使ってないし、カウンタ自身もポインタでは定義されていない。
そして、MinervaStaticDataを間に噛まさずに、HPLAir**StaticDataを直接継承すると何も問題が起こらなくなる。謎。
MinervaStaticDataはアポカリでも使おうと思って共通化のために用意したが、どうにもならないので、結局MinervaStaticDataは完全に削除した。
代わりにAir**StaticDataに色々メンバを追加した形に。
以下ポイント
※メモリ破壊に近い。
再現性が不規則。予想と全く関係無いところでエラーになる。※多重継承ではない。
多重継承は要するにclass A : public B, C;みたいに、「1つの子供に二つ以上親が居る」状態なので、今回は当てはまらない。class A:B:C:D;みたいな感じ。
class A:C:D;までは良かったのに、class A:B:C:D;ってなった途端エラーになった感じ。
■親のデストラクタ段階では、継承した子クラスで追加される領域は既に解放されている。
こっちは理由も明確だった。例えば下記。
class Parent {
public:
~Parent(){
clear();
}
virtual void clear() = 0;
};
class Child : public Parent{
public:
virtual ~Child() {}
void clear() {}
};
Childが消える時、デストラクタ~Child()が呼ばれ、その後virtualで親のデストラクタ~Parent()が呼ばれる。
しかし、親には実体がないclear()が呼ばれている。
じゃあ、継承した子クラス=Child::clear()をとなるが、子クラスの「追加部分」(clearの実体部分含む)はもう解放されており、関数の参照先が存在しない形になる。
そのため、これを実行するとNULLになったり参照エラーになる。
対処方法は幾つかある
[1]使わない
Parent p;
p.clear();
などとして、デストラクタは~Parent(){}とかにする。
×欠点として、外部から明示的に呼ぶ必要が出てくる。
[2]子の方で呼ぶ
Child::~Child(){ clear();}
Parent::~Parent(){}
とかにする。
×まあ、上手くはいくが、何のためのabstract funcなのか分からんという、デザインパターン的な意味が無くなる問題がある(気にしなきゃいいけど)
×どっちで呼べばいいのか分からなくなると言う問題もある。
[3]引数で回避する
Parent::release(bool bisClear){
if(bIsClear){
this->clear();
}
}
Parent::~Parent() { release(false);}
こうする。
要するに、他でも呼ばれるclearを使うのがまずいので、それを回避する形。
Child側で解放したいモノがあれば、~Child(){}内で解放すれば良いわけであるし。
いずれにしても分かりづらい。なるべく親は親、子は子で解放は分けた方が良い。
■他
※virtualは、子の関数「が終わった後に」親の関数を呼ぶ。
逆ではない。「先に親の関数を呼びたい」場合は下記にする。
class Parent {
void hoge() {
}
};
class Child {
void hoge(){
Parent::hoge();
処理
}
}
・Parent::hoge()が実行されたのち、処理が実行される。
・virtualが無いので、親の関数=Parent::hoge()は「明示的に指定しない限り呼ばれない」完全にオーバーライド(override)された状態となる。
上記でvirtualを付けると、Parent::hoge()→処理→Parent::hoge()ということか?(未検証)
※virtual func(){}とvirtual func()=0;
・virtual func(){}は、親の関数もこのあと呼ぶみたいな感じ。親、子、いずれも実体定義必須。・virtual func()=0;は実体を持たない。継承した子クラスが定義する必要有り。
0 件のコメント:
コメントを投稿