Reputation: 7590
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
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
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
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
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