在C ++中将不同的类放在一个容器中

时间:2010-02-14 14:21:20

标签: c++ design-patterns oop stl

有时我们必须在同一个层次结构中将不同的对象放在一个容器中。我读了一些文章,说有一些技巧和陷阱。但是,我对这个问题没有全貌。实际上,这在真正的单词中发生了很多。

例如,停车场必须包含不同类型的汽车;动物园必须包含不同类型的动物;书店必须包含不同类型的书籍。

我记得有一篇文章说以下两种都不是一个好的设计,但我忘了它的位置。

vector<vehicle> parking_lot;
vector<*vehicle> parking_lot;

有人可以为这类问题提供一些基本规则吗?

5 个答案:

答案 0 :(得分:5)

vector<vehicle>的问题是对象只保存车辆。 vector<vehicle*>的问题在于您需要分配,更重要的是,需要适当地释放指针。

这可能是可以接受的,具体取决于您的项目等......

但是,通常在处理释放的向量(vector<boost::shared_ptr<vehicle>>或Qt-something,或者您自己的某个)中使用某种smart-ptr,但仍允许在同一容器中存储不同类型的对象。

<强>更新

有些人在其他答案/评论中也提到boost::ptr_vector。这也可以作为ptr的容器,并且通过拥有所有包含的元素来解决内存释放问题。我更喜欢vector<shared_ptr<T>>,因为我可以在整个地方存储对象,并使用容器进出容器来移动它们。这是一个更通用的用法模型,我发现它对我和其他人来说更容易掌握,并且更适用于更大的问题。

答案 1 :(得分:5)

我从同一作者那里学到了很多写a similar question的回复,所以我忍不住在这里做同样的事情。简而言之,我编写了一个基准来比较以下方法来解决在标准容器中存储异构元素的问题:

  1. 为每种类型的元素创建一个类,让它们都从公共基类继承,并在std::vector<boost::shared_ptr<Base> >中存储多态基本指针。这可能是更通用和灵活的解决方案:

    struct Shape {
        ...
    };
    struct Point : public Shape { 
        ...
    };
    struct Circle : public Shape { 
        ...
    };
    std::vector<boost::shared_ptr<Shape> > shapes;    
    shapes.push_back(new Point(...));
    shapes.push_back(new Circle(...));
    shapes.front()->draw(); // virtual call
    
  2. 与(1)相同,但将多态指针存储在boost::ptr_vector<Base>中。这有点不太通用,因为元素仅由向量拥有,但它应该足以满足大部分时间。 boost::ptr_vector的一个优点是它具有std::vector<Base>的接口(没有*),因此它更易于使用。

     boost::ptr_vector<Shape> shapes;    
     shapes.push_back(new Point(...));
     shapes.push_back(new Circle(...));
     shapes.front().draw(); // virtual call
    
  3. 使用可包含所有可能元素的C联合,然后使用std::vector<UnionType>。这不是很灵活,因为我们需要事先知道所有元素类型(它们被硬编码到联合中),并且众所周知,unions与其他C ++构造没有很好的交互(例如,存储的类型不能有构造函数)。

    struct Point { 
        ...
    };
    struct Circle { 
        ...    
    };
    struct Shape {
        enum Type { PointShape, CircleShape };
        Type type;
        union {
            Point p;
            Circle c;
        } data;
    };
    std::vector<Shape> shapes;
    Point p = { 1, 2 };
    shapes.push_back(p);
    if(shapes.front().type == Shape::PointShape)
        draw_point(shapes.front());
    
  4. 使用可包含所有可能元素的boost::variant,然后使用std::vector<Variant>。这不像联合那样灵活,但处理它的代码更优雅。

    struct Point { 
        ...
    };
    struct Circle { 
        ...
    };
    typedef boost::variant<Point, Circle> Shape;
    std::vector<Shape> shapes;
    shapes.push_back(Point(1,2));
    draw_visitor(shapes.front());   // use boost::static_visitor
    
  5. 使用boost::any(可以包含任何内容),然后使用std::vector<boost::any>。这非常灵活,但界面有点笨拙且容易出错。

    struct Point { 
        ...
    };
    struct Circle { 
        ...
    };
    typedef boost::any Shape;
    std::vector<Shape> shapes;
    shapes.push_back(Point(1,2));
    if(shapes.front().type() == typeid(Point))    
        draw_point(shapes.front());   
    
  6. This is the code完整的基准程序(由于某种原因不能在键盘上运行)。这是我的表现结果:

      

    层次结构和boost :: shared_ptr的时间:0.491微秒

         

    层次结构和boost :: ptr_vector的时间:0.249微秒

         

    与联合的时间:0.043微秒

         

    使用boost :: variant的时间:0.043微秒

         

    时间与boost :: any:0.322微秒

    我的结论:

    • 仅当您需要运行时多态提供的灵活性且需要共享所有权时才使用vector<shared_ptr<Base> >。否则你将有很大的开销。

    • 如果您需要运行时多态性但不关心共享所有权,请使用boost::ptr_vector<Base>。它将明显快于shared_ptr对应物,并且界面将更友好(存储的元素不像指针那样呈现)。

    • 如果您不需要太多灵活性,请使用boost::variant<A, B, C>(即您有一小部分不会增长的类型)。它会快速点亮,代码也会很优雅。

    • 如果您需要完全灵活性(要存储任何内容),请使用boost::any

    • 不要使用工会。如果你真的需要速度,那么boost::variant就是那么快。

    在我结束之前,我想提一下std::unique_ptr的向量将是一个很好的选择,当它变得广泛可用时(我认为它已经在VS2010中)

答案 2 :(得分:2)

vehicle是一个基类,它具有某些属性,然后从它继承你说cartruck。然后你可以做类似的事情:

std::vector<vehicle *> parking_lot;
parking_lot.push_back(new car(x, y));
parking_lot.push_back(new truck(x1, y1));

这将是完全有效的,事实上有时非常有用。这种对象处理的唯一要求是对象的理智层次结构。

可以像这样使用的其他流行类型的对象是例如people :)你几乎在每本编程书中都看到了这一点。

编辑:
当然,该向量可以用boost::shared_ptrstd::tr1::shared_ptr而不是原始指针打包,以便于内存管理。事实上,我建议尽可能做的事情。

EDIT2:
我删除了一个不太相关的例子,这是一个新的例子:

假设您要实现某种AV扫描功能,并且您有多个扫描引擎。所以你实现了某种引擎管理类,比如说scan_manager可以调用那些的bool scan(...)函数。然后你创建一个引擎接口,比如engine。它会有一个virtual bool scan(...) = 0;然后你会生成一些engine,如my_super_enginemy_other_uber_engine,它们都继承自engine并实施scan(...)。然后,您的引擎管理器会在初始化期间填充std::vector<engine *> my_super_enginemy_other_uber_engine的实例,并通过顺序调用bool scan(...)或基于任何类型的扫描你想要表演。显然这些引擎在scan(...)中的作用仍然未知,唯一有趣的是bool,因此管理员可以在不做任何修改的情况下以相同的方式使用它们。

同样可以应用于各种游戏单元,例如scary_enemies,那些将是orksdrunks和其他不愉快的生物。它们都实现了void attack_good_guys(...),而您的evil_master会生成其中的许多内容并调用该方法。

这确实是一种常见的做法,只要所有这些类型实际上都是相关的,我就很难称之为糟糕的设计。

答案 3 :(得分:2)

问题是:

  1. 您不能将多态对象放入容器中,因为它们的大小可能不同 - 例如,您必须使用指针。
  2. 由于容器如何工作,普通指针/自动指针不合适。
  3. 解决方案是:

    1. 创建一个类层次结构,并在基类中至少使用一个虚函数(如果你想不到任何虚函数化函数,虚拟化析构函数 - 正如Neil指出的那样通常是必须的)。
    2. 使用boost::shared_pointer(这将在下一个c ++标准中) - shared_ptr处理被复制到 ad hoc ,容器可能会这样做。
    3. 构建一个类层次结构,并使用new在堆上分配对象 - 即。指向基类的指针必须由shared_ptr封装。
    4. 将基类shared_pointer放入您选择的容器中。

    5. 一旦你理解了上述各点的“为什么和如何”,请查看boost ptr_containers - 感谢提示手册。

答案 4 :(得分:0)

您可以参考Stroustrup对问题Why can't I assign a vector< Apple*> to a vector< Fruit*>?的回答。