2つのvisitorでデータを処理する

このQ&Aのポイント
  • Nodeクラスの派生クラス(データ)をPPVisitorとIRVisitorで処理する方法を考えます。
  • Visitorクラスには各データに対するvisit関数を宣言していますが、PPVisitorとIRVisitorが互いのvisit関数を定義すると抽象クラスになるため、一つのvisit関数を共用する方法を探します。
  • 自身のvisitor以外のvisit関数を何もしない関数として定義する方法は一つの解決策ですが、改善の余地があると考えます。
回答を見る
  • ベストアンサー

2つのvisitorでデータを処理する

class Node{ public: virtual void* accept(Visitor* v)=0; //PPVisitor用 virtual void* accept(Visitor* v, void* obj)=0; //IRVisitor用 }; 上記の派生クラス(データ)を、Visitorクラスから派生したPPVisitorとIRVisitorで処理しようと思っています。 Visitorクラスには各データに対するvisit関数を仮想関数として宣言しているのですが、この方法だとPPVisitorとIRVisitorが互いのvisit関数を定義しなければ抽象クラスになってしまいます。 Visitorクラスからそれぞれを呼び出せるようにしつつPPVisitorとIRVisitorを分離する方法は無いでしょうか。 自身のvisitorではないvisit関数は何もしない関数として定義するという方法で一応動きましたが、あまり良くない気がします。

  • honor
  • お礼率97% (69/71)

質問者が選んだベストアンサー

  • ベストアンサー
  • qwertfk
  • ベストアンサー率67% (55/81)
回答No.2

void* IDNode::accept(Visitor* v){ return v->visit(this); } void* OpNode::accept(Visitor* v){ return v->visit(this); } ... void* IDNode::accept(Visitor* v, void* obj){ return v->visit(this, obj); } void* OpNode::accept(Visitor* v, void* obj){ return v->visit(this, obj); } ... というのであれば、オーバーロードをやめて、 ・常にvisitは(Visitor*, void*)を引数にする ・第2引数を使わないケースの場合は第2引数にはNULLを設定する のような仕様にしてはいかがでしょうか。 つまり、Node側は void* IDNode::accept(Visitor* v, void* obj=NULL){ return v->visit(this, obj); } void* OpNode::accept(Visitor* v, void* obj=NULL){ return v->visit(this, obj); } ... のようにしておき、Visitor側は全て void* visit(Node* n, void* obj)=0; とする。 もう1つの改善案は、そもそもPPVisitor、IRVisitorをそれぞれVisitorの派生クラスにしない、というのはどうでしょうか? つまり、Node側は void* IDNode::acceptForPP(PPVisitor* v){ return v->visit(this); } void* OpNode::acceptForPP(PPVisitor* v){ return v->visit(this); } ... void* IDNode::acceptForIR(IRVisitor* v, void* obj){ return v->visit(this, obj); } void* OpNode::acceptForIR(IRVisitor* v, void* obj){ return v->visit(this, obj); } ... としておき、Visitor側は class PPVisitor { public: virtual void* visit(OpNode* n)=0; virtual void* visit(IDNode* n)=0; }; class IRVisitor { public: virtual void* visit(OpNode* n, void* obj)=0; virtual void* visit(IDNode* n, void* obj)=0; }; このようにするのはどうでしょうか。

honor
質問者

お礼

>・常にvisitは(Visitor*, void*)を引数にする >・第2引数を使わないケースの場合は第2引数にはNULLを設定する こちらの方法で綺麗に纏めることが出来そうです。 回答ありがとうございました。

その他の回答 (1)

  • qwertfk
  • ベストアンサー率67% (55/81)
回答No.1

質問内容が良くわからないので確認させてください。 ・まず、Visitor はこのようになっている class Visitor { public: virtual void visit(Node* n)=0; }; class PPVisitor : public Visitor{ ... }; class IRVisitor : public Visitor{ ... }; ・そして、Nodeという基底クラスがあり、それらの派生クラスがある class Node{...}; class NodeTypeA : public Node{...}; class NodeTypeB : public Node{...}; ・各ノードがVisitorを受け入れるときに、 PPVisitorクラスのオブジェクトは、 virtual void* accept(Visitor* v)=0; IRVisitorクラスのオブジェクトは、 virtual void* accept(Visitor* v, void* obj)=0; で受け入れなければならないようになっている。 ・ノードの各実装クラスはPPVisitor、IRVisitorのどちらかのみを受け入れたい場合があるが その場合上記のオーバーロードのどちらかを空の実装にするのが面倒。 class NodeTypeA : public Node { public: virtual void* accept(Visitor* v) { ... 何か処理をする } virutal void* accept(Visitor* v, void* obj) { // IRVisitorは使わないが定義しないとコンパイルできないので空の関数を作る } }; ここまでで何かまちがっていますでしょうか? 以降、上記が正しいという前提の回答になります。 まず、NodeがどのVisitorが来るのかを意識するような実装になっていてはいけません。 virtual void* accept(Visitor* v)=0; という関数があるのであれば、VisitorのインタフェースにはVisitorとして必要なものだけを定義し、 NodeはVisitorのインタフェースのみを考慮するような実装でなければなりません。 それでもどうしても上記のオーバーロードの構造になる、という場合、たとえば、 class NotImplementedException : public std::exception { ... }; class Node { public: virtual void* accept(Visitor* v) { throw NotImplementedException; } virtual void* accept(Visitor* v, void* obj) { throw NotImplementedException; } }; class NodeTypeA : public Node { public: virtual void* accept(Visitor* v) { ... 処理を書く } }; class NodeTypeB : public Node { public: virtual void* accept(Visitor* v, void* obj) { ... 処理を書く } }; のようにNode自体を抽象クラスではなく、普通の基底クラスにすれば空の関数を定義する必要はなくなります。

honor
質問者

お礼

各Nodeクラスのaccept関数は void* IDNode::accept(Visitor* v){ return v->visit(this); } void* OpNode::accept(Visitor* v){ return v->visit(this); } ... void* IDNode::accept(Visitor* v, void* obj){ return v->visit(this, obj); } void* OpNode::accept(Visitor* v, void* obj){ return v->visit(this, obj); } ... のように定義しています。また、Visitorは class Visitor{ public: virtual void* visit( OpNode* n)=0; virtual void* visit( IDNode* n)=0; ... virtual void* visit( OpNode* n, void* obj)=0; virtual void* visit( IDNode* n, void* obj)=0; ... }; 上記のように各Nodeに対応した関数を宣言しています。 PPVisitorでは引数が一つのほうのacceptおよびvisitを使えればよく、IRVisitorでは引数が2つのほうを使いたいのです。 NodeもVisitorも抽象クラスをやめれば良かったのでしょうか。

関連するQ&A

  • あるクラスの派生クラスと、その他の型で処理を振り分

    ■環境 Windows7 Visual Studio 2010 Visual C++(C++11) 大抵の型は共通処理を行いたいが、ある特定の型だけは特別な処理を行いたい、 そんなケースでは、テンプレートの明示的な特殊化が利用出来るかと思います。 しかし、『ある特定の抽象クラスの派生クラス』だけは特別な処理を行いたい、 これを実現するには、どのようにするのがよろしいでしょうか? 以下のコードは、意図した通りに動作しません。 良い方法があれば、ご教授いただけますでしょうか? // 抽象クラス class AbstractClass { public: virtual void MustOverides() = 0; }; // 派生クラス class ChildClass : public AbstractClass { public: virtual void MustOverides(){}; }; template<typename AnyType> void CommonFunc(const AnyType& target) { // 呼ばれる } template<> void CommonFunc<AbstractClass>(const AbstractClass& target) { // 呼ばれない、呼ばせたい } void CommonFunc(AbstractClass& target) { // 呼ばれない、呼ばせたい } void CommonFunc(const AbstractClass& target) { // 呼ばれない、呼ばせたい } void main() { ChildClass Child; CommonFunc(Child); } よろしくお願いいたします。

  • 派生クラスで,基本クラスのメンバ変数である構造体の型を・・・

    派生クラスで,基本クラスのメンバ変数である構造体の型を戻り値の型とするメンバ関数を作りたいのですがエラーが発生してしまいます. class A{ protected: struct NODE{ char *name; NODE *next; } NODE *node; }; class B : public A{ public: NODE* sort(NODE* fnode); }; NODE* B::sort(NODE* fnode){ ←エラー ~~~~~ return ○○; } 現在このような状態です. よろしくお願いします.

  • CFromViewの OnDrawメソッドについて

    MFC初心者です。VC2005でグラフィックスに挑戦しております。 まだよくクラスとか、MFCの作法などしらないのでトンチンカンな質問をしますが、 ドキュメント(で正しいでしょうか?)がCViewクラスの場合、 OnDraw(CDC* pDC)が定義されていたので、直接そこに描画コードを書きましたが、 CFromViewクラス(コントロール可能)の場合、 OnDraw(CDC* pDC)が見当たりませんので、定義にvoid OnDraw(CDC* pDC)を追加し、 以下のようにメソッドを追加して、無理やり表示させました。 この方法は普通でしょうか? void CtestView::OnDraw(CDC* pDC) { CBrush myBrush; CBrush* pOldBrush; myBrush.CreateSolidBrush(RGB(255,0,0)); // ソリッドブラシの作成 pOldBrush = pDC->SelectObject(&myBrush); // myBrushを選択 pDC->Ellipse(60,10,160,110); pDC->SelectObject(pOldBrush); // 元に戻す myBrush.DeleteObject(); // ブラシを削除 } //--------------------------------------------------- 以下クラス定義部 class CtestView : public CFormView { protected: // シリアル化からのみ作成します。 CtestView(); DECLARE_DYNCREATE(CtestView) public: enum{ IDD = IDD_test_FORM }; // 属性 public: CtestDoc* GetDocument() const; // 操作 public: // オーバーライド public: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV サポート virtual void OnInitialUpdate(); // 構築後に初めて呼び出されます。 // 実装 public: virtual ~CtestView(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif void OnDraw(CDC* pDC); /////////////////////////// ←勝手に追加 protected: // 生成された、メッセージ割り当て関数 protected: DECLARE_MESSAGE_MAP() }; #ifndef _DEBUG // testView.cpp のデバッグ バージョン inline CtestDoc* CtestView::GetDocument() const { return reinterpret_cast<CtestDoc*>(m_pDocument); } #endif

  • virtual時のoverrideとnewの違い

    C#で、 virtualがない時はnewしか使えませんが、 virtualがある時はoverrideとnewが使えますが、 overrideとnewを使った時の違いがよく分かりません。 例えば、基本クラスに public virtual void show(){  Console.WriteLine("基本クラス"); } というメソッドがあるとして、 派生クラスで、 public override void show(){  Console.WriteLine("派生クラス"); } とすると、 基本クラスのメソッドが上書きされて、 Console.WriteLine("派生クラス"); が実行されますが、 派生クラスで、 public new void show(){  Console.WriteLine("派生クラス"); } とした場合にも、 基本クラスのメソッドが隠匿されて、 Console.WriteLine("派生クラス"); だけが実行されます。 virtual使用時において、 継承メソッドの隠匿(new)と、 継承メソッドの上書き(override)では、 一見、なにも違いがないように見えるのですが、 何か違うのでしょうか。

  • 多態性を利用して派生クラスの関数を呼びたい

    環境はVS2013,言語はC++です. 基底クラス側で,基底クラスのポインタを引数に受ける仮想関数を宣言し, 派生クラスでその実装をします. 派生クラス側で引数に派生クラスのポインタを受けて,派生クラスのメンバにアクセスできるようにしたいのです. 作成したサンプルを下記に示します. Hello()は関係ありません. また,これはあくまで簡略化したサンプルなだけなので,thisポインタからメンバにアクセスすればよいというものではありません. #include <iostream> class Base { public: int i; Base() :i(0) {} virtual void print(Base *p) { std::cerr << "Base : " << i << std::endl; } virtual void hello() = 0; }; class Super : public Base { public: int x; Super() : x(1) {} void print(Super *p) { std::cerr << "Super : " << p->x << std::endl; } void hello() { std::cerr << "hello" << std::endl; } }; int main(void) { Base *p; p = new Super; p->print(p); } 上記を実行した結果,Base::print()が呼び出されました. print関数の引数に派生クラスSuperの実態を差すBase型ポインタを与えたときに,Super::print()を呼び出せるようにするには,何か方法はありますか. p->print(p)の呼び出し部分をp->((Super*)p)にキャストしても結果は同じくBase::print()が呼ばれました. Base::print()の実装を無くした場合は,未解決の外部参照のコンパイルエラーが発生し, Base::print()を純粋仮想関数にした場合は,Super側でSuper::pirnt(Base*p)を実装しなければならないのか,抽象クラスのインスタンス化ができないというコンパイルエラーが発生します. これは,派生クラスとはいえ,引数リストの型が違うのでオーバーライドされていないということですよね. 引数をBase *p,Super *pからそれぞれvoid *pへ変更し,Super::print()内で(Super *)へキャストしなおしたところ,きちんとオーバーライドされ,うまくはいきました. しかしながら,この方法だと派生クラス側で実装する際に毎回キャスト処理を書かなくてはなりません. 他に何かきれいな方法はないでしょうか.

  • C++/CLIでクラス内の要素を相互利用する方法

    C++/CLIでクラスの中に定義された構造体等を、複数のクラス間で相互利用したいのですが、そのようなことは可能なのでしょうか。 とりあえず以下のコードを見ていただきたいのですが、 ref class class1; ref class class2; ref class class1 { public:  enum struct enum1  {   aa,bb  };  void func1a(class1^ obj){} // 1. OK  void func2a(class2^ obj){} // 2. OK  void func1b(class1::enum1 e){} // 3. OK  void func2b(class2::enum2 e){} // 4. ERROR }; ref class class2 { public:  enum struct enum2  {   aa,bb  };  void func1a(class1^ obj){} // 5. OK  void func2a(class2^ obj){} // 6. OK  void func1b(class1::enum1 e){} // 7. OK  void func2b(class2::enum2 e){} // 8. OK }; これの4.がコンパイルエラーになります。 このような構造を定義することはできないのでしょうか。

  • 仮想関数について困っています

    仮想関数について困っています C++を現在勉強中でその中で困ったことができました。 仮想関数についてまだ分かっていないことが多いのですが、一応以下のように使うものだと学びました。 class test{  public:   virtual void run(){    std::cout<<"testクラス"<<std::endl;   } }; class test_sub:public test{  public:   void run(){    std::cout<<"test_subクラス"<<std::endl;   } }; int main(){  test *t_s;  t_s=new test_sub;  t_s->run(); } ※includeは省略させていただきます こうすれば「test_subクラス」と出力されるはずです。 そこで本題なのですが自作のarrayクラスのようなものはテンプレートクラスになっているのですが array<test*> data; data[0].run(); のように使うと「testクラス」と表示されてしまいます。 これを解決する方法は何かないでしょうか? ご存じの方がいましたら教えていただけると助かります。

  • 可変長引数をもつオーバライド関数について

    オーバライド関数について質問があります。 以下のようなクラスAとBがあります。 クラスAは基本クラスです。 クラスBはクラスAから導出しました。 ------------------------------------ CLASS A { public: virtual void func(char* p, ...); } ------------------------------------ CLASS B : public A { public: void func(char* p, ...); } ------------------------------------ メンバ関数の func() はオーバライド関数で、可変長の引数を持っています。 今、クラスBの func() の中で、クラスAの func() をコールするようにコーディングしました。 ----------------------------------- void B::func(char* p, ...) { : : A::func( ???? ) : : } ----------------------------------- ところが、クラスBの func()が受け取った引数を、そのままクラスAの func() に渡したいのですが、どうしたらいいのか分かりません。これって無理でしょうか? 教えて下さい。

  • クラスを配列で確保する場合のコンストラクタへの引数の渡し方

    例えばコンストラクタのオーバーロードで以下のようなクラスを宣言したとします。 class testClass{ public: testClass(void){ num = 10; } testClass(int num1,int num2){ num = num1 + num2; } int num; }; オブジェクトとして宣言する場合 testClass obj(10,10); と定義とすれば、testClass(int num1,int num2)の方が適用されobj.num = 20となり、問題ありませんが、ここでobjを複数定義したい場合、obj[10]と定義すると上手く引数を渡す事ができません。 以下のような形で宣言するという手もありますが testClass obj[2] = {testClass(2,2),testClass(2,2)}; 例えば定数NUMと定義しておき、 testClass obj[NUM]; という形で宣言したい場合、どのようにすればよいでしょうか? よろしくお願いします。

  • 抽象クラスについて

    抽象クラスを使って派生クラスを作成していて疑問になったのですが、派生クラスで作るメンバ関数は、抽象クラスで仮想関数としてすべて定義しておくべきなのでしょうか。ご教授願います。

専門家に質問してみよう