C++:对象向量与指向新对象指针的向量?

C++: Vector of objects vs. vector of pointers to new objects?

我想通过编写一个示例软件渲染器来提高我的C++技能。它获取由三维空间中的点组成的对象,并将其映射到二维视区,并为视图中的每个点绘制不同大小的圆。哪个更好:

1
2
3
4
5
6
7
class World{
    vector<ObjectBaseClass> object_list;
public:
    void generate(){
        object_list.clear();
        object_list.push_back(DerivedClass1());
        object_list.push_back(DerivedClass2());

或者…

1
2
3
4
5
6
7
class World{
    vector<ObjectBaseClass*> object_list;
public:
    void generate(){
        object_list.clear();
        object_list.push_back(new DerivedClass1());
        object_list.push_back(new DerivedClass2());

??在第二个例子中使用指针来创建新的对象会击败使用向量的点,因为向量会在第一个例子中自动调用派生类析构函数,而不是在第二个例子中?使用向量时是否需要指向新对象的指针,因为只要使用向量的访问方法,向量本身就处理内存管理?现在假设我有另一种方法:

1
2
3
4
5
void drawfrom(Viewport& view){
    for (unsigned int i=0;i<object_list.size();++i){
        object_list.at(i).draw(view);
    }
}

调用时,将为世界列表中的每个对象运行draw方法。假设我希望派生类能够拥有自己的draw()版本。那么为了使用方法选择器(->)列表需要指针吗?


由于您明确地声明希望改进C++,所以我建议您使用Boost启动。这可以通过三种不同的方式帮助您解决问题:

使用shared_ptr

使用shared_ptr可以这样声明向量:

1
std::vector< boost::shared_ptr< ObjectBase > > object_list;

像这样使用:

1
2
3
4
typedef std::vector< boost::shared_ptr< ObjectBase > >::iterator ObjectIterator;

for ( ObjectIterator it = object_list.begin(); it != object_list.end(); it++ )
    (*it)->draw(view);

这将给您提供多态性,并像使用指针的正常向量一样使用它,但shared_ptr将为您进行内存管理,在最后一个shared_ptr引用对象时销毁该对象。

关于C++ 11的注释:在C++ 11中,EDOCX1×2成为EDCOX1×7的标准的一部分,因此这种方法不再需要Boost。但是,除非你真的需要共享所有权,建议使用EDCOX1 OR 8,这是在C++ 11中新引入的。

使用ptr_vector

使用ptr_vector,您可以这样做:

1
boost::ptr_vector< ObjectBase > object_list;

像这样使用:

1
2
3
4
typedef boost::ptr_vector< ObjectBase >::iterator ObjectIterator;

for ( ObjectIterator it = object_list.begin(); it != object_list.end(); it++ )
    (*it)->draw(view);

这将再次被用作指针的法向量,但这次ptr_vector管理对象的生存期。与第一种方法不同的是,在这里,当向量被破坏时,对象会被破坏,而在上面,如果存在其他shared_ptr引用对象,对象可能比容器寿命更长。

使用reference_wrapper

使用EDOCX1[13]您可以这样声明:

1
std::vector< boost::reference_wrapper< ObjectBase > > object_list;

然后这样使用:

1
2
3
4
5
typedef std::vector< boost::reference_wrapper< ObjectBase > >::iterator
    ObjectIterator;

for ( ObjectIterator it = object_list.begin(); it != object_list.end(); it++ )
    it->draw(view);

注意,您不必像上面的方法那样首先取消对迭代器的引用。但是,只有在其他地方管理对象的生命周期,并且保证比vector的生命周期长时,这才有效。

关于C++ 11的注释:EDCOX1,13,也在C++ 11中被标准化,现在可以用作EDCOX1,17,没有升压。

正如Maciejhs回答中指出的,第一种方法会导致对象切片。通常,您可能希望在使用容器时查看迭代器。


你不能用这个代码得到你想要的

1
2
3
4
5
6
7
class World{
    vector<ObjectBaseClass> object_list;
public:
    void generate(){
        object_list.clear();
        object_list.push_back(DerivedClass1());
        object_list.push_back(DerivedClass2());

将要发生的事情称为对象切片。您将得到一个ObjectBaseClass的向量。

要使多态性工作,必须使用某种指针。Boost或其他库中可能有一些智能指针或引用,可以使用它们,使代码比第二个建议的解决方案更安全。


对于第一个问题,通常最好使用自动分配的对象,而不是动态分配的对象(换句话说,不存储指针),只要对于所讨论的类型,复制构造和分配是可能的,并且不会造成过高的代价。

如果对象不能被复制或分配,那么你就不能直接把它们放到std::vector中,所以这个问题是没有意义的。如果复制和/或分配操作很昂贵(例如,对象存储大量数据),那么出于效率原因,您可能希望存储指针。否则,通常最好不要因为您提到的原因而存储指针(自动解除分配)

对于第二个问题,是的,这是存储指针的另一个有效原因。动态调度(虚拟方法调用)只在指针和引用上工作(并且不能在std::vector中存储引用)。如果需要在同一个向量中存储多个多态类型的对象,则必须存储指针以避免切片。


这取决于你想用向量做什么。

如果您不使用指针,那么它是您传入的对象的一个副本,该对象被放到向量上。如果它是一个简单的对象,并且/或者您不想费心跟踪它们的存储,那么这可能正是您想要的。如果构建和销毁是一件复杂的事情,或者非常耗时,那么您可能更喜欢只执行一次这项工作,并将指针传递到向量中。