In a modern system, random memory access is now the killer. The CPU has cycles to burn. Some of the lessons I've learned on performance recently taught the exact opposite of what was true twenty years ago.
To that end, a modern style of dealing with containers has to be bounded. You need to know where you are, and what your limits are. There's some issue with what operator++() should do when you reach those limits, but the one answer for sure is that it can't just go stepping past and start stomping on whatever's next. To that end, my current libraries have the following concepts for both buffers and containers:
A Range is a beginning and and end.
These never own the storage, they just indicate where it is. This can be used for both the available space to write to, and for the used space containing data within a buffer or container.
A cursor is a range + an iterator.
This is where I've gone a bit past all the other work in C++ containers, but I think this is important. Modern iterators (at least the fun ones), are bidirectional or random access. That means the beginning is as important to keep track of as the end. And copying a cursor should not narrow you to the space you had left, but should allow the copy to head backwards to the front just as easily as progressing to the end.
This also gives us a great data structure for those algorithms like std::rotate() that operate on three iterators.
There have been a lot of people banging around on this for some time. Andrei Alexandrescu wrote a great paper On Iteration that had lots of stuff to say about his implementation of containers and interators for D.
What I and many others ended up with after diving in there was encumbered lists. This is generally frowned upon by advanced C++ people who point out that there is a standard library with a number of "high quality" containers in them. But we arrived at encumbered lists, because we were making lists of polymorphic objects. The STL containers are all made for uniform types.
To provide a bridge, the boost people threw the PIMPL hammer at it.
As I understand it, the original use of PIMPL was to "hide" full class definition from users of the class (usually at library boundaries), and also to significantly speed up compile times for large systems. Compiling has never been an instantaneous process (sadly). Over my career, I've had to deal with more than one system that took hours to rebuild, so knocking time off is more than an academic curiousity.
Applied to the problem of polymorphic objects in containers, you end up with a PIMPL front-ing object, with a hidden implementation behind an abstract interface. Throw in a smart pointer as well to manage life-cycle and you suddenly have problem of memory coherency, because for your first object allocated you get:
I think there's a better way, but its a shame that no one else has actually built anything that gives you the power of containers, the algorythms, but plays well with polymorphic objects.