anthonyvd
anthonyvd

Reputation: 7590

What's the preferred C++ idiom to own a collection of polymorphic objects?

Consider the following classes

class Base {
public:
    virtual void do_stuff() = 0;
};

class Derived : public Base {
public
    virtual void do_stuff() { std::cout << "I'm useful"; };
};

Now let's say I want to have another class responsible for owning objects of Base's derived types and iterate through them calling their do_stuff() method. It looks like this, but I don't know what T should be declared as

class Owner {
public:
    void do_all_stuff() {
        //iterate through all items and call do_stuff() on them
    }

    void add_item(T item) {
        items.push_back(item);
    }

    vector<T> items;
}

I see a few possibilities:

T can't be Base, since I would only be able to add objects of concrete type Base, so that's out of the question.

T can be Base* or Base&, but now I need to trust the caller of add_item() to pass me a pointer or a reference to an object that will still exist when I retrieve it from items. I can't delete the elements in Owner's destructor, since I don't know that they were dynamically allocated. However, they should be delete'd if they were, which leaves me with ambiguous ownership.

T can be Base* or Base& and I add a Base* create_item<DerivedT>() { return new DerivedT; } method to Owner. This way, I know the pointer will remain valid and I own it, but I'm unable to call a non-default constructor on DerivedT. Also, Owner becomes responsible for instantiating objects as well. I also have to delete every item in Owner's destructor, although that's not much of an issue.

Basically, I'd like to be able to do something similar to:

Owner owner;

void add_one() {
    Derived d;

    owner.add_item(d);
}

void ready() {
    owner.do_all_stuff();
}

void main() {
    for(int i = 0; i < 10; ++i) {
        add_one();
    }
    ready();
}

I'm sure there's something related to move semantics in there (I could move the objects passed to add_items() to own them) but I still can't figure out how my collection would be declared.

What is the C++ idiom for this sort of polymorphic ownership (particularly with STL containers)?

Upvotes: 19

Views: 3236

Answers (4)

TPJ
TPJ

Reputation: 179

Other alternatives worth considering are to use boost::ptr_container, or even better, use a library like adobe::poly or boost::type_erasure for your polymorphic types, to exploit value-based run-time polymorphism—avoids the need for pointers, inheritance, etc.

Upvotes: 2

bames53
bames53

Reputation: 88155

Polymorphic objects have to be handled by pointer or reference. Since their lifetime is probably not bound to a particular scope they will also probably have dynamic storage duration, which means you should use a smart pointer.

Smart pointers such as std::shared_ptr and std::unique_ptr work just fine in the standard collection types.

std::vector<std::unique_ptr<Base>>

Using this in Owner looks like:

class Owner {
public:
    void do_all_stuff() {
        //iterate through all items and call do_stuff() on them
    }

    void add_item(std::unique_ptr<Base> item) {
        items.push_back(std::move(item));
    }

    vector<std::unique_ptr<Base>> items;
}

The argument type to add_item identifies the ownership policy required for adding an item, and requires the user to go out of their way to screw it up. For example they can't accidentally pass a raw pointer with some implicit, incompatible ownership semantics because unique_ptr has an explicit constructor.

unique_ptr will also take care of deleting the objects owned by Owner. Although you do need to ensure that Base has a virtual destructor. With your current definition you will get undefined behavior. Polymorphic objects should pretty much always have a virtual destructor.

Upvotes: 20

Mark B
Mark B

Reputation: 96241

Assuming from your context that Owner is the sole owner of the contained objects,T should be unique_ptr<Base> (where unique_ptr comes from boost or std depending on your C++11 availability). This properly recognizes that it's solely owned by the container and additionally shows the ownership transferral semantics in your add_item call.

Upvotes: 2

Philip Stuyck
Philip Stuyck

Reputation: 7457

Actually you cannot store references in STL, only pointers or real values. So T is Base* Try other things you will have your compiler complaining.

Upvotes: 1

Related Questions