1

ダイヤモンドの継承が存在する場合に冗長な関数呼び出しを防ぐための良い戦略は何ですか? 具体的には、次のプログラムがあるとします。

#include <iostream>

struct A {
    int a;
    A(int a_) : a(a_) {}
    virtual void print() {
        std::cout << "a:  " << a << std::endl;
    }
};

struct B : virtual public A {
    int b;
    B(int a_,int b_) : A(a_), b(b_) {}
    virtual void print() {
        A::print();
        std::cout << "b:  " << b << std::endl;
    }
};

struct C : virtual public A {
    int c;
    C(int a_,int c_) : A(a_), c(c_) {}
    virtual void print() {
        A::print();
        std::cout << "c:  " << c << std::endl;
    }
};

struct D : public B,public C {
    int d;
    D(int a_,int b_,int c_,int d_) : A(a_), B(a_,b_), C(a_,c_), d(d_) {}
    void print() {
        B::print();
        C::print();
        std::cout << "d:  " << d << std::endl;
    }
};

int main() {
    D d(1,2,3,4);
    d.print();
}

d.print() を呼び出すと、次のようになります。

a:  1
b:  2
a:  1
c:  3
d:  4

ここで、a が 2 回印刷されています。これを防ぐ良い方法はありますか?確かに、次のようなコードを使用して接続を手動で配線できます。

#include <iostream>

struct A {
    int a;
    A(int a_) : a(a_) {}
    virtual void print_() {
        std::cout << "a:  " << a << std::endl;
    }
    virtual void print() {
        A::print_();
    }
};

struct B : virtual public A {
    int b;
    B(int a_,int b_) : A(a_), b(b_) {}
    virtual void print_() {
        std::cout << "b:  " << b << std::endl;
    }
    virtual void print() {
        A::print_();
        B::print_();
    }
};

struct C : virtual public A {
    int c;
    C(int a_,int c_) : A(a_), c(c_) {}
    virtual void print_() {
        std::cout << "c:  " << c << std::endl;
    }
    virtual void print() {
        A::print_();
        C::print_();
    }
};

struct D : public B,public C {
    int d;
    D(int a_,int b_,int c_,int d_) : A(a_), B(a_,b_), C(a_,c_), d(d_) {}
    virtual void print_() {
        std::cout << "d:  " << d << std::endl;
    }
    virtual void print() {
        A::print_();
        B::print_();
        C::print_();
        D::print_();
    }
};

int main() {
    D d(1,2,3,4);
    d.print();
}

正しく出力する

a:  1
b:  2
c:  3
d:  4

しかし、もっと良い方法があるかどうか知りたいです。これがどこで発生するかという観点から、オブジェクト A、B、C、および D が複雑であり、それら自体をディスクに書き込むことができる必要がある状況を想像してください。各 A、B、C、および D の出力コードを 1 回だけ記述したいだけであり、D が A に関する情報を 2 回記述しないことが重要です。

<---編集--->

この問題を解決する方法はあと 2 つありますが、まだわかりにくいものです。最初のものはクリスチャンからのもので、A が印刷されているかどうかにフラグを設定することを含みます。

#include <iostream>

struct A {
    int a;
    bool have_printed;
    A(int a_) : have_printed(false), a(a_) {}
    virtual void print() {
        if(have_printed) return;
        std::cout << "a:  " << a << std::endl;
        have_printed=true;
    }
    void clear() {
        have_printed=false;
    }
};

struct B : virtual public A {
    int b;
    B(int a_,int b_) : A(a_), b(b_) {}
    virtual void print() {
        A::print();
        std::cout << "b:  " << b << std::endl;
    }
};

struct C : virtual public A {
    int c;
    C(int a_,int c_) : A(a_), c(c_) {}
    virtual void print() {
        A::print();
        std::cout << "c:  " << c << std::endl;
    }
};

struct D : public B,public C {
    int d;
    D(int a_,int b_,int c_,int d_) : A(a_), B(a_,b_), C(a_,c_), d(d_) {}
    void print() {
        B::print();
        C::print();
        std::cout << "d:  " << d << std::endl;
    }
};

int main() {
    D d(1,2,3,4);
    d.clear();
    d.print();
}

これは正しく出力されます。2 番目の方法はより複雑ですが、構造が大きくなる可能性があります。基本的に、クラスからプリンターを分離し、各オブジェクト内にプリンターのリストを登録します。印刷したいときは、プリンターのリストを反復処理し、正しい出力を提供します。これはあまりにも多くの機械を使用していると思いますが、他の誰かがより良いアイデアを得る場合に備えて含めます:

// A simple unary function.  Technically, the stl version doesn't require
// the operator
template <typename A,typename B>
struct unary {
    virtual B operator () (A a) {};
};

struct A {
    // Data
    int a;

    // A list of pointers to unary functions.  We need pointers to unary
    // functions rather than unary functions since we need the printer
    // to be polymorphic.
    std::list < unary<A*,void>* > printers;
    A(int a_);

    // We actually end up allocating memory for the printers, which is held
    // internally.  Here, we free that memory.
    ~A() {
        for(std::list < unary<A*,void>* >::iterator printer
                =printers.begin();
            printer != printers.end();
            printer++
        )
            delete (*printer);
    }

private:
    // Require for the dynamic cast used later
    virtual void ___dummy() {};
};
// Prints out the data for A
struct A_Printer : public unary<A*,void>{
    void operator () (A* a) {
        std::cout << "a:  " << a->a << std::endl;
    }
};
// Adds the printer for a to the list of printers
A::A(int a_) : a(a_) {
    printers.push_back(new A_Printer()); 
}

// Now that the structure is setup, we just need to define the data for b,
// it's printer, and then register the printer with the rest
struct B : virtual public A {
    int b;
    B(int a_,int b_);
};
struct B_Printer : public unary<A*,void>{
    void operator () (A* b) {
        std::cout << "b:  " << dynamic_cast <B*>(b)->b << std::endl;
    }
};
B::B(int a_,int b_) : A(a_), b(b_) {
    printers.push_back(new B_Printer());
}

// See the discussion for B
struct C : virtual public A {
    int c;
    C(int a_,int c_);
};
struct C_Printer : public unary<A*,void>{
    void operator () (A* c) {
        std::cout << "c:  " << dynamic_cast <C*>(c)->c << std::endl;
    }
};
C::C(int a_,int c_) : A(a_), c(c_) {
    printers.push_back(new C_Printer());
}

// See the discussion for B
struct D : public B,public C {
    int d;
    D(int a_,int b_,int c_,int d_);
};
struct D_Printer : public unary<A*,void>{
    void operator () (A* d) {
        std::cout << "d:  " << dynamic_cast <D*>(d)->d << std::endl;
    }
};
D::D(int a_,int b_,int c_,int d_) : A(a_), B(a_,b_), C(a_,c_), d(d_) {
    printers.push_back(new D_Printer());
}

// This actually prints everything.  Basically, we iterate over the printers
// and call each one in term on the input.
void print(A* a) {
    for(std::list < unary<A*,void>* >::iterator printer
            =a->printers.begin();
        printer != a->printers.end();
        printer++
    )
        (*(*printer))(a);
}

int main() {
    D d(1,2,3,4);
    // This should print 1,2,3,4
    print(&d);
}

<---編集2--->

tmpearce は、ハッシュ テーブルを組み立てる前に、すべての情報をハッシュ テーブルに蓄積するという良い考えを持っていました。これにより、個別情報が作成済みかどうかを確認し、重複を防ぐことができます。情報が簡単に集められるのであれば、これは良い考えだと思います。そうでない場合は、tmpearce と Cristian のアイデアを組み合わせたわずかなバリエーションが機能する可能性があります。ここでは、関数が呼び出されたかどうかを追跡するセット (またはハッシュテーブルなど) を渡します。このようにして、何らかの関数が計算されたかどうかを確認できます。永続的な状態を必要としないため、複数回呼び出しても安全です。

#include <iostream>
#include <set>

struct A {
    int a;
    A(int a_) : a(a_) {}
    virtual void print_(std::set <std::string>& computed) {
        if(computed.count("A") > 0) return;
        computed.insert("A");
        std::cout << "a:  " << a << std::endl;
    }
    void print() {
        std::set <std::string> computed;
        print_(computed);
    }
};

struct B : virtual public A {
    int b;
    B(int a_,int b_) : A(a_), b(b_) {}
    virtual void print_(std::set <std::string>& computed) {
        A::print_(computed);
        if(computed.count("B") > 0) return;
        computed.insert("B");
        std::cout << "b:  " << b << std::endl;
    }
};

struct C : virtual public A {
    int c;
    C(int a_,int c_) : A(a_), c(c_) {}
    virtual void print_(std::set <std::string>& computed) {
        A::print_(computed);
        if(computed.count("C") > 0) return;
        computed.insert("C");
        std::cout << "c:  " << c << std::endl;
    }
};

struct D : public B,public C {
    int d;
    D(int a_,int b_,int c_,int d_) : A(a_), B(a_,b_), C(a_,c_), d(d_) {}
    virtual void print_(std::set <std::string>& computed) {
        B::print_(computed);
        C::print_(computed);
        if(computed.count("D") > 0) return;
        computed.insert("D");
        std::cout << "d:  " << d << std::endl;
    }
};

int main() {
    D d(1,2,3,4);
    d.print();
}

いずれにせよ、この問題を解決済みとしてマークします。ただし、追加の回答を常に聞きたいと思っています。

4

3 に答える 3

0

私のアプローチは、あなたが言及したものを組み合わせるようなものです。仮想メソッドに少し異なることをさせます。

class A
{
   public:
   virtual void getInfo(std::map<std::string,std::string>& output)
   {
      if(output.count("A") == 0)
      {
         output["A"] = "a: 1";
      }
   }
   void print()
   {
      std::map<std::string,std::string> m;
      getInfo(m); //virtual method (most derived will be called)
      std::map<std::string,std::string>::iterator iter = m.begin();
      for(; iter!=m.end(); ++iter)
      {
         std::cout<<iter->second();
      }
   }
};

class B : public A
{
   virtual void getInfo(std::map<std::string,std::string>& output)
   {
      A::getInfo(output);
      if(output.count("B") == 0)
      {
         output["B"] = "b: 2";
      }
   }
};

printは現在、コンテナにデータを入力するために使用する非仮想メソッドでgetInfoあり、それを繰り返して出力を表示/保存します。したがって、各クラスは、文字列を書き込んでコンテナに追加する前に、そのレベルの継承チェーンに必要な出力がコンテナに含まれていないことを確認できます。

于 2012-06-08T23:51:35.240 に答える
0

A構造体(およびダイアモンドが1レベルを超えている場合は、BとC)にプライベートフラグを追加し、それをチェックして、すでに通過したものとしてマークします。これは、より複雑な(ネストされた)ダイヤモンドパターンにも役立ちます。

このような:

struct A {
    int a;
    A(int a_) : a(a_) {traversed = false;}
    virtual void print() {
        if (traversed) return;
        std::cout << "a:  " << a << std::endl;
        traversed = true;
    }
private:
    bool traversed;
};
于 2012-06-08T18:01:58.273 に答える
0

1 つのクラスのみが仮想ベース (最も派生したものD) を構築するため、1 つのクラスのみがオブジェクトを出力し、Aその構築と同様に、最初にそれを実行するようにします (オブジェクトをディスクに書き込む場合はおそらく重要です)。

void*のコンストラクターに引数を追加し、Aそれを のメンバーに格納できAます。各派生クラスは、仮想ベースを として構築しA(a, this)ます。

に新しいメンバー関数を追加し、Aすべてdo_print(void*)の派生クラスがdo_print(this)の代わりに呼び出されるようにしA::print()ます。このdo_print(void*)関数は、その引数をctor にvoid*渡されて格納されているものと比較しA、同じ場合にのみ出力します。これは、すべてのクラスが空でない場合に真となる個別のアドレスを持つすべての派生クラスに依存しますが、その仮定が保持される場合、最も派生したオブジェクトのみが仮想ベースを出力することが保証されます。

于 2012-06-20T00:22:04.800 に答える