2010-07-05 42 views
3

我有不同的類型,說ABC,所有的一些基礎類Base繼承:這個容器的最簡潔的實現是什麼類型的容器?

class Base { ... }; 
class A : public Base { ... }; 
class B : public Base { ... }; 
class C : public Base { ... }; 

我需要一個容器,我們稱之爲Master,持有指針的類型AB對象和C。我希望Master容器提供一個覆蓋所有包含Base對象的迭代器,以及包含所有包含的ABC對象的特定類型的迭代器。作爲存儲後端,我將使用std::vector,但如果稍後可以輕鬆切換,那將會很不錯。

從概念上講,這是Master應該呈現給外界的接口:

class Master { 

    public: 

    add(A *a); 
    add(B *b); 
    add(C *c); 

    remove(Base *base); 

    iterator<A*> a_begin(); 
    iterator<A*> a_end(); 
    iterator<B*> b_begin(); 
    iterator<B*> b_end(); 
    iterator<C*> c_begin(); 
    iterator<C*> c_end(); 
    iterator<Base*> base_begin(); 
    iterator<Base*> base_end(); 
    // also: reverse iterators, const iterators, reverse const iterators 
}; 

接口不必匹配這個精確的語法。例如,someMaster.begin<A>()也很好。

問題是,即使在這個簡化的界面中,您已經可以看到一些代碼重複的發生。在實施中更糟糕。這是不可接受的,因爲如果我想添加類D,EF(也繼承自Base),我希望能夠稍後容易地擴展Master容器。我最好用一行或兩行代碼來擴展它。

所有這些都可以用很多dynamic_cast ing來實現,但這很醜陋。我認爲模板和多繼承的一些魔法可以幫助我在這裏。這個班最乾淨的實施是什麼?

+0

容器中元素的順序是否相關?特別是,通過'base_begin()'和'base_end()'迭代的元素的預期順序是什麼?元素可以按類型重新排序嗎? – 2010-07-05 08:57:15

+0

如果您打算將A,B和C視爲不同類型而不是Base *,那麼它就會破壞多態性。 – Puppy 2010-07-05 09:30:24

回答

4

這裏是什麼,我會做一個素描:

// Beware, brain-compiled code ahead 
template< typename T > 
class typed_container 
{ 
    typedef std::vector<T> data_t; 
public: 
    typedef data_t::iterator iterator; 
    iterator begin() {return data_.begin();} 
    iterator end () {return data_.end();} 
private: 
    data_t data_; 
}; 

typedef my_type_list<A,B,C> types_t; 

class master : public derive_from< typed_container, types_t > { 
    template< typename T > 
    struct traits { 
    typedef typename typed_container<T>::iterator iterator; 
    iterator begin(typed_container<T>& c) {return c.begin();} 
    iterator end (typed_container<T>& c) {return c.end ();} 
    }; 
public: 
    template< typename T > 
    typename traits<T>::iterator begin() {return traits<T>::begin(*this);} 
    template< typename T > 
    typename traits<T>::iterator end () {return traits<T>::end (*this);} 

    typedef my_assembling_iterator<types_t> iterator; 

    iterator begin() {return my_assembling_iterator<types_t>.begin(*this);} 
    iterator end () {return my_assembling_iterator<types_t>.end (*this);} 
}; 

這使得您能夠實現my_type_list(很簡單),derive_from(不是那麼簡單,但不要太用力要麼),和my_assembling_iterator(我hadn」還需要做類似的事情)。


你可以找到一個工作的C++ 03型列表實現here。只需要多達九個模板參數(不過這很容易擴展),你得寫

typedef my_type_list<A,B,C>::result_t types_t 

,但它的簡單和自由,我知道它(因爲我使用這個庫我自己)。

derive_from模板會是這個樣子:

//Beware, brain-compiled code ahead! 
template< template<typename> class C, class > 
struct derive_from; 

template< template<typename> class C > 
struct derive_from< C, nil > {}; 

template< template<typename> class C, typename Head, typename Tail > 
struct derive_from< C, my_type_list<Head,Tail> > : public C<Head> 
               , public derive_from<C,Tail> {}; 

剩下的迭代器。你有什麼需要呢?它必須是一個隨機訪問迭代器(硬)還是一個前向迭代器就足夠了?你是否需要任何特定的順序來遍歷元素?

+0

我一直希望有這樣的事情......以及擔心它。類型列表會有點混亂,因爲我沒有使用C++ 0x,所以沒有可變模板:(感謝您的答案,並提供幫助實現,但我想我可以從中找出它這很可能與我自己的結果非常相似,但我認爲可能會有一個更容易的,思考的解決方案。 – Thomas 2010-07-05 09:04:57

+1

類型列表實際上並不需要C++ 0x。可以看看現代C++編程的類型列表,用法和'derive_from'模板。我很長時間沒有看Loki庫,但它應該已經有兩個實現。迭代器適配器不應該很難您不介意按子類排序的項目(與插入順序相比),否則您將不得不使用指向基本類型的額外向量並使用添加到每個子容器的所有條目來更新它。 – 2010-07-05 09:09:25

+1

也可以採用'boost :: variant'路徑並生成迭代器適配器。 'begin()'會返回一個適配器,對於每個存儲的變體返回所包含的指針,'begin '和相關類型將使用Variant中的訪問者模式,這樣當向量遞增時,它將跳過不是'A'型......如果你對這條路有興趣,我可以畫一個解決方案。 – 2010-07-05 09:13:42

4

首先你應該看看Boost.Variant
它允許您將多個不相關的對象存儲在容器中。
迭代器自己必須嘗試使用​​boost::get<T>()來處理類型爲T的下一個對象。
如果對象不是T類型,則重複前進。
如果要迭代所有對象,只需返回這些對象的變體,那樣A,B和C就不必與Base相關(如果不需要它們)或者只需要一個指向Base的指針(如果全部用例將有一個共同的基類。

+1

其實我會說它不適合這裏,每次將類添加到集合中時都需要修改'Master'。促進。變體是針對不相關的類型。 – 2010-07-05 16:34:29

+0

不可用於可變參數模板或可以定義主容器內使用的類的列表的類型列表。 – 2010-07-05 21:43:27

0

我看到至少有兩個問題在這裏:

如何避免在接口的「冗餘」?

主類有一些功能,這似乎冗餘a_begin/a_endb_begin/b_endc_begin/c_end)。我的第一個建議是在這種情況下使用Java風格的迭代器,以便您將end函數移出master的接口到相應的迭代器中。

考慮這個例子:

master *m = ..; 
master::iterator<A*> a = m->getA(): 
while (a->hasNext()) { 
    A *current = a->next(); 
    current->doSomething() 
} 

這樣的話,你必須每個具體類型(ABC)只是一個迭代器吸氣。

我懷疑,有些人認爲,迭代器getter可能會成爲一個成員模板,所以你只有一個get()模板並能夠說m->get<A>()。出於性能方面的考慮,我想你應該專門化這些獲取器來迭代特定的容器(見下面)。

如何使迭代器有效地迭代?

Base迭代器應該迭代所有包含的對象,具體迭代器應該只遍歷包含對象的子集。做到這一點,我建議使master包含倍數std::vector對象,每個具體類型一個。

您的A迭代器可以遍歷包含所有A*的向量,對於其他具體類型也是如此。迭代器只會遍歷所有容器,將它們視爲一個連續的容器。

+0

這是最簡單的方法。但是,當C++ 1x完全到達時,這種方法不會像其他方法那樣靈活。 – 2010-07-05 10:47:59

2

爲什麼不能RTTI + boost filter_iterator?人們通常害怕RTTI,但filter_iterator和模板過濾器比較兩個type_infos會很好。

+1

由於RTTI可能很慢。 Scott Meyers曾經報道VC有時會在執行'dynamic_cast <>'的時候使用字符串比較(當涉及到DLL時)。像這樣迭代容器中的對象可能會或可能不會。 – sbi 2010-07-05 10:11:00

+0

比較兩個type_info通常相當於比較兩個指針。沒有涉及到dynamic_cast。如果OP希望能夠從A,B或C繼承,並使迭代器在一些涉及的層次結構中工作,dynamic_casting將像其他任何替代方法一樣慢。 – 2010-07-05 10:20:12

+0

(你應該正確地@attribute評論迴應,我只是偶然發現了這個)。只要它是一個二進制文件,它就是一個簡單的指針比較。據我所知,當DLL發揮作用時,VC會降級爲字符串比較。 – sbi 2010-07-05 11:43:45

相關問題