2014-05-09 45 views
0

我有一個樹類樹,我希望能夠以不同的方式構建它。 build()函數將在Tree的構造函數中調用。課堂上的設計問題和功能

結果在空間和數據結構方面將完全相同,但構建樹的方式會有所不同(每個元素將放置在哪個節點/葉中)。手前已知節點和葉子的數量。

但是,build()具有特定的原型。我希望用戶只要看一下interface並知道他必須實施什麼。

所以,我正在考慮去與template。在編寫代碼之後,我注意到樹的用戶沒有任何接口來查看build()的原型。當然,我可以寫在文檔中或讓他/她面對編譯錯誤,但這不是一個好主意,恕我直言。

在這種情況下,用戶會做:

Tree<SplitClass> tree; // ideal, if I could somehow notify him for the prototype 

於是,我想到了abstract類和(pure) virtual methods。再次,這工作,但現在用戶必須做這樣的事情:

Base* a = new SplitClass; 
Tree(a); 

在這裏,我不喜歡,是用戶所要做的使用new和用戶可能不是那麼好節目。而且,用戶不能立即執行,例如template

最後,我嘗試了一個function pointer,它可以工作,但不知道接口。

當然,還有一種解決方案是在其他文件中聲明和定義分割函數幷包含它們。

[編輯]

只是爲了定義一個函數(該項目很重要的),應該創建一個類的事實,是一個壞主意?

[EDIT.2]

構建的原型()只需要std::vector和一些size_t變量。在那個階段,我只是在構建這棵樹,所以我沒有任何有關它如何在以後使用的例子。

[EDIT.3]

最小工作實例,它使用了template。此外,關鍵字virtual也發揮作用。

這將起作用,但用戶可能實現他自己的一個類,它不會從Base繼承並通過類Calc。我不希望他能夠做到這一點。

CalcTree這個類我在實際項目中有AB這個分裂類。

#include <iostream> 

template<class Operation> 
class Calc { 
public: 
    Calc(int arg) : 
     a(arg) { 
     Operation o(a, 10); 
     o.add(a); 
    } 

    void print() { 
     std::cout << a << "\n"; 
    } 

private: 
    int a; 
}; 

class Base { 
protected: 
    virtual void add(int& a) = 0; 
}; 

class A: public Base { 
public: 
    A(int& a, int c) { 
     a = a + c; 
    } 

    virtual void add(int& a) { 
     a += 100; 
    } 
}; 

class B: public Base { 
public: 
    B(int& a, int c) { 
     a = a - c; 
    } 

    virtual void add(int& a) { 
     a += 100000; 
    } 

}; 

int main() { 

    Calc<A> a(2); 
    a.print(); 

    Calc<B> b(2); 
    b.print(); 

    return 0; 
} 

[編輯。4]

因爲,沒有其他建議,這是我應該從我已經有的人應該遵循的嗎?最好的選擇,根據OOP「規則」。

我的目標不僅是做出設計決定,而且還要接受教育,其中一個方面就是要走在OOP世界。

[EDIT.5]

而現在,我覺得這兩個分裂類,應該採取不同數量的參數(多了一個第二個!)。

如果您認爲這個問題不具有建設性或廣泛,讓我知道,我會刪除它。

+0

所以用戶'實現或SplitClass'其他一些類/函數,然後把它傳遞給樹的構造,以建立樹?你能告訴我們更多的例子,以及這個類/函數是如何使用的? – dyp

+0

@dyp確切!我沒有任何其他的小例子。我將編輯原型的樣子。我正處於樹木建設的階段,所以現在只有創作才能發揮作用。我想要一個簡單的方法來構建樹(就像一個用'template'),但不知何故,我想構建()被強制爲具有特定原型(如'virtual'關鍵字允許我們做的)並且用戶能夠通知構造函數應該使用哪個build()。感謝您的評論。 – gsamaras

+0

你可能尋找一個*概念*,這將有望在今年被指定爲ISO技術規範(但他們不會是C++ 14標準本身的一部分)。 – dyp

回答

1

既然沒有其他建議,那我應該從我已經擁有的那個中選擇一個呢?就OOP「規則」而言,這是最好的選擇。

C++是一種多範型編程語言,所以您不需要始終使用類層次結構和虛函數(幸運的是)。如果問題是分層的,或者可以用分層形式很好地描述,則使用OO。

爲了專門研究C++中的算法,通常使用函數對象。函數對象是一個函數指針或類對象重載operator()

#include <iostream> 

class Calc { 
public: 
    template<class Op> 
    Calc(int arg, Op op) 
     : a(arg) { 
     op(a); 
    } 

    void print() { 
     std::cout << a << "\n"; 
    } 

private: 
    int a; 
}; 

如果函數對象是在構造函數中只用,你並不需要保存它,因此你不需要知道其類型Calc。然後,我們可以只對構造函數進行「模板化」,而不是對整個類進行「模板化」。

但是,這個構造函數的第二個參數是不受限制的:如果op(a)無效,它可以取任何東西,但不能編譯。要限制Op的類型,概念當前正在指定中。希望它們將在今年(2014年)發佈爲ISO技術規範。

在我們拿到它們之前,我們可以使用醜陋的SFINAE技術和static_asserts來使錯誤信息更好。


可以在這裏使用繼承來表達一個概念,雖然。通過使用模板,您仍然可以避免虛函數調用。例如:

class MyInterface { 
protected: 
    virtual void add(int& a) = 0; 
}; 

class A final 
    : public MyInterface 
{ 
public: 
    A(int& a, int c) { 
     a = a + c; 
    } 

    virtual void add(int& a) final override { 
     a += 100; 
    } 
}; 

通過使A最後,或者只是add,編譯器可以(可能)推斷,這是虛擬函數的最終置換器,並避免調度。

class Calc { 
public: 
    template<class Op> 
    Calc(int arg, Op op) 
     : a(arg) { 
     static_assert(std::is_base_of<MyInterface, Op>(), 
         "Op must implement MyInterface"); 
     op(a); 
    } 

    void print() { 
     std::cout << a << "\n"; 
    } 

private: 
    int a; 
}; 

我們可以很容易地檢查一類是從別的類派生而來,這可以作爲一個概念檢查的簡化版本。

但是,這個構造函數仍然是貪婪的:它產生一個重載排列爲第二個參數中所有類型的精確匹配。如果這成爲一個問題,你將不得不使用SFINAE技術來減少貪婪。

+0

最後一件事。看來這個班級還需要一個參數。所以,我在考慮變量參數列表或者讓兩者都接受相同數量的參數,並且僅僅在一個類中(在類的函數中)評論額外的參數。我感覺到了這一點。列表會產生一些開銷。你認爲這是一個壞主意嗎? – gsamaras

+0

@ G.Samaras如果你的意思是'va_args'等功能,你不應該使用它,如果你可以避免它。它有很多問題。你應該把所有參數從ctor傳遞給'Op',這可能會用在'Op'中。如果您選擇抽象基類方法,這當然意味着所有派生類都將獲得所有參數。如果其中一個派生類不需要所有參數,它可以忽略它們 - 例如不要在覆蓋中給參數一個名字。如果您不使用ABC方法,則可以使用可變參數模板來接受其他參數。 – dyp