C++: Vector of objects vs. vector of pointers to n

2019-01-21 09:38发布

问题:

I am seeking to improve my C++ skills by writing a sample software renderer. It takes objects consisting of points in a 3d space and maps them to a 2d viewport and draws circles of varying size for each point in view. Which is better:

class World{
    vector<ObjectBaseClass> object_list;
public:
    void generate(){
        object_list.clear();
        object_list.push_back(DerivedClass1());
        object_list.push_back(DerivedClass2());

or...

class World{
    vector<ObjectBaseClass*> object_list;
public:
    void generate(){
        object_list.clear();
        object_list.push_back(new DerivedClass1());
        object_list.push_back(new DerivedClass2());

?? Would be using pointers in the 2nd example to create new objects defeat the point of using vectors, because vectors automatically call the DerivedClass destructors in the first example but not in the 2nd? Are pointers to new objects necessary when using vectors because they handle memory management themselves as long as you use their access methods? Now let's say I have another method in world:

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

When called this will run the draw method for every object in the world list. Let's say I want derived classes to be able to have their own versions of draw(). Would the list need to be of pointers then in order to use the method selector (->) ?

回答1:

You wont get what You want with this code

class World{
    vector<ObjectBaseClass> object_list;
public:
    void generate(){
        object_list.clear();
        object_list.push_back(DerivedClass1());
        object_list.push_back(DerivedClass2());

What is going to happen is called object slicing. You will get a vector of ObjectBaseClass.

To make polymorphism work You have to use some kind of pointers. There are probably some smart pointers or references in boost or other libraries that can be used and make the code much safer than the second proposed solution.



回答2:

Since you are explicitly stating you want to improve your C++, I am going to recommend you start using Boost. This can help you with your problem in three different ways:

Using a shared_ptr

Using a shared_ptr could declare your vector like this:

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

And use it like this:

typedef std::vector< boost::shared_ptr< ObjectBase > >::iterator ObjectIterator;

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

This would give you polymorphism and would be used just like it was a normal vector of pointers, but the shared_ptr would do the memory-management for you, destroying the object when the last shared_ptr referencing it is destroyed.

Note about C++11: In C++11 shared_ptr became part of the standard as std::shared_ptr, so Boost is no longer required for this approach. However, unless you really need shared ownership, it is recommended you use std::unique_ptr, which was newly introduced in C++11.

Using a ptr_vector

Using a ptr_vector you would do it like this:

boost::ptr_vector< ObjectBase > object_list;

And use it like this:

typedef boost::ptr_vector< ObjectBase >::iterator ObjectIterator;

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

This would again be used like a normal vector of pointers, but this time the ptr_vector manages the lifetime of your objects. The difference to the first approach is, that here your objects get destroyed when the vector gets destroyed, whereas above they may live longer than the container, if other shared_ptrs referencing them exist.

Using a reference_wrapper

Using a reference_wrapper you would declare it like this:

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

And then use it like this:

typedef std::vector< boost::reference_wrapper< ObjectBase > >::iterator 
    ObjectIterator;

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

Notice that you do not have to dereference the iterator first as in the above approaches. This does however only work if the lifetime of your objects is managed elsewhere and is guaranteed to be longer than that of the vector.

Note about C++11: reference_wrapper has also been standardized in C++11 and is now usable as std::reference_wrapper without Boost.

As pointed out in Maciej Hs answer, your first approach results in object slicing. In general you may want to look into iterators when using containers.



回答3:

As for your first question, it is generally preferred to use automatically allocated objects rather than dynamically allocated objects (in other words, not to store pointers) so long as for the type in question, copy-construction and assignment is possible and not prohibitively expensive.

If the objects can't be copied or assigned, then you can't put them directly into a std::vector anyway, and so the question is moot. If the copying and/or assignment operations are expensive (e.g. the object stores a large amount of data), then you might want to store pointers for efficiency reasons. Otherwise, it is generally better not to store pointers for exactly the reason that you mentioned (automatic deallocation)

As for your second question, yes, that is another valid reason to store pointers. Dynamic dispatch (virtual method calls) work only on pointers and references (and you can't store references in a std::vector). If you need to store objects of multiple polymorphic types in the same vector, you must store pointers in order to avoid slicing.



回答4:

Well, it depends on what you are trying to do with your vector.

If you don't use pointers, then it is a copy of the object you pass in that gets put on the vector. If it is a simple object, and/or you don't want to bother with keeping track of the storage for them, this may be exactly what you want. If it is something complex, or very time-consuming to construct and destruct, you might prefer to do that work only once each and pass pointers into the vector.