Polymorphic STL containers and iterators

Template classes to build polymorphic data structures based on STL, enabling transparent use of STL algorithms.

Introduction

STL standard containers store objects of a fixed type, specified in their template parameter. They do not, therefore, support polymorphism by design: you can't store objects from a class hierarchy easily.

The first, naive idea is to define the container to store pointers to the base class:

typedef std::list<T*> polyTlist;

But this will pose two major problems:

Pointers will be copied as the container is being used, but the pointed objects (pointees) won't. This will cause memory leaks and multiple reference problems, for example:

polyTList a;
a.push_back(new T(1));
polyTList b=a; // the pointer to T(1) is copied in b list
*b.front()=T(2); // but *a.front() is also re-assigned!

Standard algorithms will work on the pointers, not on the pointees. Unless you want to sort objects by their address in memory, this isn't very useful...

Smart Pointers strike again!

Storing smart pointers in the container will partially solve the first problem. Ideally, we'd need smart pointers using either on the "deep copy" or "copy on write (cow)" ownership policy, as described in [1], to mimic the behavior of the standard containers. However, they require the base class to implement some kind of cloning method, which may have some impact on performance.

We prefer reference counted pointers such as [2], which mainly add automatic deallocation to standard pointer behavior. This ensures optimal performance in algorithms, and we will prevent accidents with multiple references through another mechanism, implemented in the iterators as described below:

Polymorphic iterators

Accesses to data stored in containers is done through iterator objects. By defining our own iterators to support polymorphism transparently, standard algorithms can be applied to polymorphic containers without modification.

Let's first implement the const_iterator, which gives read-only access to the data. A first attempt could be:

which basically overrides the dereferencing operators * and -> of the standard const_iterator of a given container C to perform a double dereferencing, giving access to the stored data.

However, there is a problem with certain implementation of the STL which use real pointers as iterators, especially for std::vector: C++ does not allow to derive a class from a built-in type, and the above template definition might therefore fail. We have to implement the const_iterator this way:

The const_iterator satisfies our requirement of safety about multiple references since it gives access to const data. Ideally, if we don't provide non-const iterators to polymorphic containers, we would be safe. However, many algorithms require iterators to exist for any container. Our solution is to define iterators with exactly the same interface as const_iterators:

Here, the required implicit conversion operator poses a minor threat to the data safety as it gives access to the underlying smart pointer, which might be used in a dereferencing + assignment. However, as long as the polymorphic containers and iterators can be used transparently (i.e. the same way as standard containers) in a safe way, we consider that access to the underlying data structure can be allowed to users who "know what they are doing".

Enter the factory

To insert polymorphic objects in our structure, we need a way to clone them, i.e., a way to copy them while preserving their type. Since every class hierarchy might require a different way to clone its classes differently, we implement a generic class that wraps a class factory which will be used by default in the polymorphic container, but which can easily be overridden when needed:

By default, the factory calls a method called "Factory" of the class hierarchy, which returns a smart pointer on a new copy of the object passed as parameter.

And now, the container(s)

Of course, we could implement a polymorphic_list, a polymorphic_vector, a polymorphic_queue, and so on, but laziness is the quality of the generic programmer, so we defined a single, template-based polymorphic_container that can derive from (almost) any standard container:

We added some methods to insert, push_back and push_front elements already present in other polymorphic containers, to avoid the need for a deep copy mechanism, as well as methods to insert directly objects through the use of the factory.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Comments and Discussions

An alternative to trying to make the algorithms handle pointers to objects is to use the predicate version of the algorithms when dealing with containers of pointers and use a specialization of the comparision algorithm of your choice (less<T> is the default one for sort) to deal with the dereferencing prior to sorting.

If you decide to become a software engineer, you are signing up to have a 1/2" piece of silicon tell you exactly how stupid you really are for 8 hours a day, 5 days a week

The polymorphic STL container does not make any assumption about the base class.
It uses the operator= of the smart pointer template class only.
Therefore, the copy policy is defined by the smart pointer class you choose to use.
So it should work with your smart pointers.

I forgot to discuss the Factory helper class, sorry.
In order to transparently use algorithms, the container needs a way to allocate from a reference to the base class.
Therefore I added the Factory template class, with a default implementation that indeed calls a static Factory method in the base class.
If your code uses algorithms that require the a factory, you'll need to write a Factory template class tailored to your needs.

You're calling it a factory method, but that sounds like what a clone method would do.
So that means that in order to use your Polymorphic Container, the base class does need a clone method. Is that correct?

I'm still trying to figure out why you stated the performance comment about clone methods.
Can you please expand on this?

Top ten member of C++ Expert Exchange.
http://www.experts-exchange.com/Cplusplus

Thanks for getting back to me. But has been pointed out by others, the sources on sourceforge are old. The polycontainer.h in the posted DYL library is not up to date with the suggestions in this article.

If I understand your response, it sounds like what you are suggesting is that I (or someone else reading this) try and pull together an updated polycontainer.h from the article, and post that on sourceforge.

Certainly anybody could download the old sources from sourceforge, update the file, and post it. But since you wrote the article, I assume that you already have updated sources that are tested and working.

If that's the case, aren't you in the best position to just post your latest (or at least an updated polycontainer.h)?

If you have problems getting implementation for the code in this article, then I recommend you use a clone_ptr smart pointer.
IMHO, the clone_ptr smart pointer is a much better interface for a ploymorphic STL container, and it's more generic.

The copy_ptr is a newer more optimized version of the clone_ptr smart pointer.
The clone pointers do not need for the base class or it's derived classes to have a clone() function.

The boost library also has a set of polymorphic containers, but I think the implementation in this article is better then that of the boost library version.
Unfortunatly, I also haven't been able to download a version of the code that I could compile and test.

Top ten member of C++ Expert Exchange.
http://www.experts-exchange.com/Cplusplus

Right, the article wasn't finished, sorry.
I just updated and complemented it now, especially the section on the factory class.
The source code is in my "DYL" library available on http://www.dynabits.com/dyl
I want to keep it there as it is an ongoing process.

About the available iterator operators, I did not implement them all as it wasn't the goal of the article and also as many weren't needed in my case (lazyness). I'm aware of no std algorithm which needs to compare iterators (which are usually addresses). However, I think std operators on iterators might either be called directly through the operators which convert poly_iterators into "regular" iterators on the underlying container of smart pointers, or trivial to implement.

If there is no other major objection on the approach proposed in the article, I'd probably add the operators you mention and some other details and submit poly_container to Boost.org

Finally, the use of typedefs goes IMHO further than shortening type names. They let you preserve readability and coherence of types in large code. I wrote poly_container because I had a large library (http://www.dynabits.com/cadoo, commercial...) in which several non-polymorphic data structures had to evolve to polymorphic, while hundreds of algorithms were already based on them. Since I had defined for example: