2010-07-05 48 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

相关问题