2012-06-08 32 views
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 

其中一個已經打印了兩次。有沒有防止這種情況的好方法?當然,我們可以手動接線連接與像代碼:

#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編寫輸出代碼,重要的是D不要兩次寫入有關A的信息。

< ---編輯--->

這裏有兩個解決問題的辦法,但它們仍然是一種鈍。第一個來自Cristian,涉及設置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(); 
} 

正確輸出。第二種方法更爲複雜,但可能會使結構增長。基本上,我們將打印機從課程中分離出來,然後在每個對象內註冊打印機列表。當我們想要打印時,我們遍歷打印機列表,然後給我們正確的輸出。我覺得這佔用過多的機械,但我會包括案件別人得到一個更好的主意:

// 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); 
} 

< --- EDIT 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(); 
} 

在任何情況下,爲解決我會標示出這個問題了。雖然,我一直想聽到更多的答案。

+0

__dummy是保留名稱,您不能使用它 –

回答

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填充的容器中,然後遍歷它顯示/輸出保存非虛方法。因此,在寫入字符串並將其添加到容器之前,每個類都可以檢查以確保容器尚未包含繼承鏈的該級別所需的輸出。

0

我想給一個結構添加一個私有標誌(如果鑽石延伸到一個層次上,則給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; 
}; 
+0

您下次打印時如何重置標記?哪個班級負責決定是否需要重新設置? –

0

只有一個類構造虛擬基礎(衍生得最多,D),所以我確保只有一個類打印A對象,並且像構造一樣,首先使其發生(如果您將對象寫入磁盤可能很重要)

您可以將void*參數添加到A的構造函數中,並將其存儲在A的成員中。每個派生類將構建虛擬基礎爲A(a, this)

添加一個新的成員函數到A,do_print(void*),並且每個派生類都調用do_print(this)而不是A::print()do_print(void*)函數將其參數與傳遞給A ctor的存儲的void*進行比較,並且只在相同時纔打印。這依賴於具有不同地址的每個派生類,如果所有的類都是非空的,這將是真的,但如果這個假設成立,它將確保只有最派生的對象打印虛擬基地。

相關問題