1) it's not necessary to store the same vptr (8 bytes per object?) collection.size() times;
It is not necessary, but objects are independent of one another.
2) it's possible to use contiguous storage locations, since their size are known and equal to each other when their type is defined.
... indeed, if you can store concrete instances you can store them in contiguous memory.
So, what can you do ?
One solution is to not use polymorphic instances, but instead have data and polymorphism separated:
struct IShape {
virtual ~IShape() {}
virtual double area() const = 0;
};
struct Circle {
float center, radius;
};
struct IShapeCircle: IShape {
IShapeCircle(Circle const& c): circle(const_cast<Circle&>(c)) {}
virtual double area() const { return PI * circle.radius * circle.radius; }
Circle& circle;
};
This way, you only create a polymorphic instance when you need it. And for the storage, we can adapt Massimiliano's solution.
struct IShapeVector {
virtual ~IShapeVector() {}
std::unique_ptr<IShape> get(size_t i) = 0;
std::unique_ptr<IShape const> get(size_t i) const = 0;
};
struct IShapeCircleVector: IShapeVector {
std::unique_ptr<IShape> get(size_t i) {
return make_unique<IShapeCircle>(_circles.at(i));
}
std::unique_ptr<IShape const> get(size_t i) const {
return make_unique<IShapeCircle const>(_circles.at(i));
}
std::vector<Circle> _circles;
};
However, you might find that the allocation/deallocation traffic slows you down more than the mere v-ptr.