JAVAとBREWのソース互換性について005
だいぶ実機でのチェックも済み、Object型くらいなら公開しても大丈夫かなという状況になりました。
VS6.0だとコンパイル通るのに、ARMコンパイラだとエラーになる場面が多すぎるんですよね。苦労しました。
特に「曖昧な定義です」系。
さて、まず目指すはJAVAとのソース互換性です。
やねうらお先生が作ったスマートポインターは素晴らしいものですが、やはりobj->hoge();という記述になってしまうのが問題です。
残念ながら . のオーバーライドは無いようです。ちぇ。
というわけで、頑張って「ポインター(のふりをしたクラス)にメソッドを作る」ようにしなければobj.hoge();は記述できないということになります。
ポインター(のふりをしたクラス)を今後擬似ポインターと呼びます。
次にインスタンスをどう実装するかです。
「参照されている限り消えない実体」であり「色んなところから参照される実体」です。
つまり擬似ポインターが指している、唯一の実体ですね。
例えばJAVAで…。
とやっても"ほげ"自体は1つしか生成されないわけです。
String str1 = new String("ほげ");
String str2 = str1;
さて、これをC++で実装してみましょう。
こうやって、実際の"ほげ"にはm_pDataからアクセスすればいいわけです。
class Object{
protected:
ObjectData* m_pData;
public:
// 省略
};
した時にはm_pDataをコピーすればいいわけですね。
str2 = str1;
そして今回使うのは単純な参照カウント式なのでその際参照カウントを増加させます。
参照カウントとは、実体側が「俺、一体いくつのポインターから参照されてるんだろう」というデータを保持しておき、どこからも参照されなくなったら自身をdeleteするというものです。
では実体側を実装してみましょう。
m_nCntがカウンターですね。自身を参照するポインターが増えるごとに+1され、減るごとに-1されます。
class ObjectData{
public:
int m_nCnt;
ObjectData() : m_nCnt(0){}
virtual ~ObjectData(){}
};
そして0になったら擬似ポインター側がObjectDataをdeleteしてくれます。
Stringクラスを作る時には
こんな感じに継承させます。
class StringData : public ObjectData{
public:
char* m_pszString;
StringData(){
m_pszString = NULL;
}
virtual ~ObjectData(){
if (NULL != m_pszString)
delete [] m_pszString;
}
};
class String : piblic Object{
// 省略
}:
そしてm_pszStringのように、必要な変数を追加します。
ではObjectとObjectDataをまとめてどうぞ。
ObjectDataについては解説したので、Objectについて解説します。
class ObjectData{
public:
int m_nCnt;
ObjectData() : m_nCnt(0){}
virtual ~ObjectData(){}
};class Object{
protected:
ObjectData* m_pData;int inc();
int dec();
void insert_pointer(Object* pObject);
void insert_reference(const Object& obj);private:
Object& operator*(){return *this;};
Object* operator&(){return this;};
public:/*
コンストラクタ. デストラクタ.
*/
Object() : m_pData(NULL){};
Object(const Object& obj){
m_pData = obj.m_pData;
inc();
}
Object(Object* obj)
m_pData = NULL;
insert_pointer(pObject);
}
virtual ~Object(){
dec();
}/*
Object型の代入演算子.
*/
Object& operator =(const Object& obj){
insert_reference(obj);
return *this;
}/*
比較演算子.
*/
friend bool operator==(const Object& obj, void *p);
friend bool operator==(void *p, const Object& obj);
friend bool operator!=(const Object& obj, void *p);
friend bool operator!=(void *p, const Object& obj);};
参照カウントの増減を行なう関数です。
int inc();
int dec();
decには参照カウントが0になったらObjectDataをdeleteするようにしてあります。
これらは*objや&objされると困るのでprivateに隠しておきました。
Object& operator*(){return *this;};
Object* operator&(){return this;};
まず、基底コンストラクタとしてはm_pDataのNULL初期化が必須ですよね。
Object() : m_pData(NULL){};
Object(const Object& obj){
m_pData = obj.m_pData;
inc();
}
Object(Object* obj){
m_pData = NULL;
insert_pointer(pObject);
}
virtual ~Object(){
dec();
}
次のコピーコンストラクタではinsert_referenceの呼び出しを行います。
アドレス渡しについては、JAVAでの…。
を再現するためです。この記述はC++では…。
String str = new String("ほげ");
になるのです。中ではinsert_pointerを呼んでいます。
String str(new String("ほげ"));
デストラクタはdecを呼び出して参照数を減らします。
こちらはinsert_referenceを呼んでいます。
Object& operator =(const Object& obj){
insert_reference(obj);
return *this;
}
では最後に、insert_reference, insert_pointerの実装を…。
void Object::insert_reference(const Object& obj){
// ↑constにしないと、ARMコンパイラがうるさい!// 同一ポインターなら無視
if (this == &obj)
return;// 今までもっていた参照を解放
dec();// ポインターのコピー
m_pData = obj.m_pData;// 参照カウントを増加させる
inc();
}
基本的に、今までの参照を解放し、新しいポインターを参照するようにします。
void Object::insert_pointer(Object* pObject){// 今までもっていた参照を解放
dec();// NULLの代入だった場合は、解放のみ
// これは obj = null; を再現するため
if (NULL == pObject){
return;
}// ポインタをコピー
m_pData = pObject->m_pData;// 参照カウントを増加させる
inc();// 参照元を削除
// obj = new Object で生成されたObject自体は不要なため
// 欲しいのはm_pDataの先に作られた実体だけだもの
delete pObject;
}
今までの御主人様をポイして新しい御主人様に尽くすわけです。
その際今までの御主人様には「あなたのメイドは一人減りましたわよ!」と通知してあげます。(decの中で行なっています)
メイドが一人も居なくなると御主人様は「もう生きてる望みも無いんじゃー!」と嘆き、最後の一人が「それなら出て行くついでに殺してあげるわ!!」と殺します。(delete m_pData;)
…。ちょっと例えに無理がありましたか。
ところでこれ、パッと見アップキャストとダウンキャストはどうなのよ思えます。私も思いました。
だってObjectもStringも「擬似ポインターであって、ポインターじゃないじゃん。ポインターじゃなきゃキャストはできないじゃん」ですよね。
え? 思わない?
思わないような人は、この日記を読んで得るものはありませんw
さて、解説です。
ですが、どちらも成功します。
Object obj;
String str = new String("ほげ");
obj = str;
str = obj;
何故なら = 代入を行なった際に実際に呼び出されるのはinsert_referenceであり、insert_referenceはただ単にm_pDataのアドレスコピー(と参照数の増加)をしているに過ぎないからです。
なら…。
これはどうだ!
obj = str;
bool b = ((String)obj).equals("ほげ");
……できます!
これ、作るまで私も知らなかったんですけど…。
の略みたいですよ。実際はstrTmpという名前は無く、無名のString変数が作られるんですが。
(String)obj
↓
String strTmp(obj);
こんなことも知らずにC++使ってのか!! すいません!!
というわけで、コピーコンストラクタでも結局insert_referenceを呼び出してるだけなので上と同じ理由で成功します。
その後すぐにstrTmpは破壊されます。
やったね。これでキャストもやりたい放題。
今うちの環境では…。
こんな感じに暴れまわっています。
String str = new String("ほげ");str += "あひょー!";
str += "ぷげら" + str + "ちょめ";
str = str + "あれ?";
newやdeleteが頻繁に呼ばれるので、きちんと自前のメモリープールを作ってエラーが起こらないようにしましょうね。
MALLOCやFREEを使っていては万が一のメモリー確保失敗でどうにもならなくなってしまいます。
この辺りについては過去日記を読んでください。BREWで日記内検索かければひっかかります。
では最後に細かいポイント。
#define null (Object*)0
としておくと楽です。
単に0にしてると、ARMコンパイラが「曖昧だこら!」とエラーを出します。
nullという記述はJAVAの名残でしょうから、Objectにしか代入しませんよね。なので(Object*)0でOKと。
やはり実際に作ってみないと勉強にならないものです。
これを作っただけでも、C++の挙動について知らないことだらけだったことを痛感しました。
これからも頑張ります(`・ω・´)
何かこの記事に間違いあったら、どんどん指摘してください。
といっても、ここ尋ねてくるプログラマなんて3人くらいしかいないやヽ(*´ー`)ノ
[追記]
void operator =(const Object& obj);
を
Object& operator =(const Object& obj);
に書き直しました。
Effective C++によると、voidを返り値とした場合代入の連鎖(a = b = c;)が出来なくなってしまうと書かれているのですが、試したところ可能でした。ARMコンパイラでもOK。
うーむ。でも念のため書き直しておきましたとさ。