お世話になります。
OOPの勉強としてデザインパターンを学習してまして、
Javaで書かれたソースをC++に移植して勉強している所
です。
質問内容は、Compositeパターンのソースを変換した所、
再帰的に名称を表示する処理がうまく出来ません。
60行目あたりの//☆の箇所で、<list>から取り出した
オブジェクトのDisplay()をコールしているのですが、
オーバーライドがうまく出来ていないのか、スーパー
クラス(Component)のDisplay()を呼んでいます。
オブジェクトの振る舞いもイマイチ良く分からず、
手探りで動かしながら作業をしているのですが、どうにも
分からずに困っています。どなたかお教え願えませんでしょうか?
以下ソース。
------------------------------------------------
#include stdafx.h
#include <iostream>
#include <string>
#include <list>
using namespace std;
class Component
{
public:
string name;
Component()
{
this->name = ";
}
Component(string name)
{
this->name = name;
}
virtual void add(Component c){};
virtual void remove(Component c){};
virtual void display(){};
};
class Composite : public Component
{
private:
list<Component> children;
public:
Composite(string name) : Component(name){};
void add(Component c)
{
children.push_back(c);
}
void remove(Component c)
{
list<Component>::iterator p;
for (p = children.begin(); p != children.end(); p++)
{
Component q = (Component)*p;
if (c.name == q.name) children.erase(p);
}
}
void display()
{
cout << this->name << endl;
list<Component>::iterator p;
for (p = children.begin(); p != children.end(); p++)
{
Component *c = &(Component)*p;
c->display(); //☆
}
}
};
class Leaf : public Component
{
public:
Leaf(string name):Component(name){}
void add(Component c){}
void remove(Component c){}
void display()
{
cout << name << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Component *root = new Composite(root);
Component *n01 = new Leaf(01);
Component *n02 = new Leaf(02);
root->add(*n01);
root->add(*n02);
Component *compA = new Composite(compA);
Component *A01 = new Leaf(A1);
Component *A02 = new Leaf(A2);
compA->add(*A01);
compA->add(*A02);
root->add(*compA);
Component *compB = new Composite(compB);
Component *B01 = new Leaf(B1);
Component *B02 = new Leaf(B2);
Component *B03 = new Leaf(B3);
compB->add(*B01);
compB->add(*B02);
compB->add(*B03);
root->add(*compB);
root->display();
delete root;
delete compA;
delete compB;
delete n01;
delete n02;
delete A01;
delete A02;
delete B01;
delete B02;
delete B03;
return 0;
}
<<現状の表示>>
root
<<期待する表示>>
root
01
02
compA
A1
A2
compB
B1
B2
B3
環境は、WinXP+VC.net2005です。宜しくお願いします。
list<Component>.push_back は、Component のコピーを格納します。
引数に Component の派生クラスを渡すと、派生型の内の Component 部分だけのコピーが
作られて格納されます(派生部分が切り落とされることを「スラッシング」と言いま
す)。
仮想関数を正常に機能させるためには、list には Component のポインタか参照を格納し
なければなりません。
#include <iostream>
#include <string>
#include <list>
using namespace std;
class Component {
public:
string name;
Component() { this->name = "; }
Component(string name) { this->name = name; }
virtual void add(Component* c){};
virtual void remove(Component* c){};
virtual void display(){};
};
class Composite : public Component {
private:
list<Component*> children;
public:
Composite(string name) : Component(name){};
void add(Component* c) { children.push_back(c); }
void remove(Component* c) {
for ( list<Component*>::iterator p = children.begin();
p != children.end(); ++p) {
if (c == *p ) { children.erase(p); break; }
}
}
void display() {
cout << this->name << endl;
for ( list<Component*>::iterator p = children.begin();
p != children.end(); ++p) {
(*p)->display(); //☆
}
}
};
class Leaf : public Component {
public:
Leaf(string name):Component(name){}
void add(Component c){}
void remove(Component c){}
void display() { cout << name << endl; }
};
int main() {
Component *root = new Composite(root);
Component *n01 = new Leaf(01);
Component *n02 = new Leaf(02);
root->add(n01);
root->add(n02);
Component *compA = new Composite(compA);
Component *A01 = new Leaf(A1);
Component *A02 = new Leaf(A2);
compA->add(A01);
compA->add(A02);
root->add(compA);
Component *compB = new Composite(compB);
Component *B01 = new Leaf(B1);
Component *B02 = new Leaf(B2);
Component *B03 = new Leaf(B3);
compB->add(B01);
compB->add(B02);
compB->add(B03);
root->add(compB);
root->display();
delete root;
delete compA;
delete compB;
delete n01;
delete n02;
delete A01;
delete A02;
delete B01;
delete B02;
delete B03;
return 0;
}
× スラッシング
○ スライシング
もしかしてですが、デザインパターンの勉強とC++言語の勉強を並行して
やってませんか?
C++言語でデザインパターンの勉強をするのであれば、C++言語の文法に関しては
大丈夫な状態にしておかないと問題点が絞り込めずに余計混乱すると思います。
少なくとも基本的な文法に関してはきちんと理解してからデザインパターンの
勉強に入ったほうが良いと思います。
今回で言うなら仮想関数をうまく使うためには派生元のクラスの
ポインタか参照で扱う必要があると言う部分は基本的な知識になります。
蛇足ですけれど、
VC.net2005ではなくてVC++2005だと思います。
2005以降は開発環境名に.netは入ってませんので。
επιστημηさん、お忘れ物です。
virtual ~Component() {}
ついでに(用途にもよるけど)これも加えて、
root以外のdeleteもなくしたいところ。
~Composite() {
for ( list<Component*>::iterator p = children.begin();
p != children.end(); ++p) {
delete *p;
}
}
おー。ふぉろーありがとです。
あ、Componentのメソッド群は pure virtual にするが吉かな。
お早い回答ありがとうございます。
無事に動作する様になりました。ホント助かりました。
◎シャノンさん
>仮想関数を正常に機能させるためには、list には Component のポインタか参照を
>格納しなければなりません。
なるほど。これが原因ですか・・・。でもエラーにはならないんですね。
◎επιστημηさん
ソース修正ありがとうございます。
ご丁寧にインデントまで直して頂いて・・・。
◎PATIOさん
>もしかしてですが、デザインパターンの勉強とC++言語の勉強を並行して
>やってませんか?
お察しの通りです。恥ずかしながら今までCメインでやってまして、
C++も形だけクラス使ってソース書いてたりはしたのですが、
継承だの仮想関数だのは殆ど使った事がなくて・・・ホントお恥ずかしい
限りです。
>VC.net2005ではなくてVC++2005だと思います。
よくよく見たらそうですね。2005も今回初めて使ってます。
ずっとVC++6.0だったもので・・・。また勝手が大分変わりましたね。
使い易くなってる気はしますけど。
◎たいちうさん
>root以外のdeleteもなくしたいところ。
なるほど・・・全然考えも及びませんでした。
そっちの方が遥かにスマートですね・・・。
別の意味で勉強になります。
お礼のコメントも長くなってしまいましたが、
皆さんご丁寧にありがとうございました。精進致します。
remove()の件をすっかり忘れてました・・・
現状だと第一階層にある物しか削除出来ませんね。
要素が枝なのか葉なのかを判別する仕組みが
必要だと思ってますが、その認識で良いのでしょうか?
こんな感じで、任意のオブジェクトの削除が出来ました。
ありがとうございました。
void remove(Node* c) {
for ( list<Node*>::iterator p = children.begin();
p != children.end(); ++p)
{
if (c->name == (*p)->name){ children.erase(p); return;}
else (*p)->remove(c);
}
}
同じ名前のノードが複数あったらアウト。
>同じ名前のノードが複数あったらアウト。
あ。確かにそうですね。
全て回りきるまでreturnしないようにすれば、
全部削除出来ますが、特定の場所のだけになると
また別途処理が必要になりますね。
またファイルとフォルダを想定した場合に
同じファイル名が同じ階層にある事はないですが、
ファイルとフォルダ名が同一って事も
考えられますね・・・。
そうなると、add()の時にフォルダなのか
ファイルなのかを判定しないとならないですね・・・
そうなるとファイルかフォルダの別をメンバに
加えてやって判定する形でしょうか。