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

sky*_*oor 1 c++ oop design-patterns stl

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

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

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

vector<vehicle> parking_lot;
vector<*vehicle> parking_lot;
Run Code Online (Sandbox Code Playgroud)

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

Mac*_*cke 5

问题vector<vehicle>在于物体只能容纳车辆.问题vector<vehicle*>是你需要分配,更重要的是,适当地释放指针.

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

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

更新

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


Man*_*uel 5

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

  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
    
    Run Code Online (Sandbox Code Playgroud)
  2. 与(1)相同,但将多态指针存储在a中boost::ptr_vector<Base>.这有点不太通用,因为元素仅由向量拥有,但它应该足以满足大部分时间.一个优点boost::ptr_vector是它具有a std::vector<Base>(没有*)的接口,因此它更易于使用.

     boost::ptr_vector<Shape> shapes;    
     shapes.push_back(new Point(...));
     shapes.push_back(new Circle(...));
     shapes.front().draw(); // virtual call
    
    Run Code Online (Sandbox Code Playgroud)
  3. 使用可以包含所有可能元素的C联合,然后使用a 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());
    
    Run Code Online (Sandbox Code Playgroud)
  4. 使用boost::variant包含所有可能元素的a,然后使用a 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
    
    Run Code Online (Sandbox Code Playgroud)
  5. 使用boost::any(可以包含任何东西)然后a 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());   
    
    Run Code Online (Sandbox Code Playgroud)

这是完整基准程序的代码(由于某种原因不能在键盘上运行).这是我的表现结果:

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

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

与联合的时间:0.043微秒

时间与boost :: variant:0.043微秒

时间与提升::任何: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中)