Thiago R. Adams website

Home CodeBlog Articles Downloads Links Books About

Websites

Comparing two approaches to implement polymorphic collections

Generally to implement polymorphic collections is necessary to create containers of pointers.
Using STL containers directly can be dangerous because, for instance, each time you want to remove an element you would worry about to remove and delete it.
One solution is to use smart pointers but this solution has a cost. On the other hand, the raw pointer implementation requires few lines more to be implemented.
You can see the implementations here and compare.
Both are equally exception safe code.
Common code for both:


//our polimorphic item
class Shape 
{ 
public:    
    virtual Shape * clone() const = 0;  
};

class Circle : public Shape 
{  
    Shape * clone() const { return new Circle(*this); }  
}; 
Raw pointer implementation of Shapes

struct delete_f {
    template <class T> void operator()(T *p) { delete p; }
}; 

class Shapes 
{ 
    typedef vector<Shape*> ShapesVec; 
    ShapesVec shapes; 

public: 

    Shapes(){} 
    Shapes(const Shapes &other) 
    { 
        Shapes temp; 
        ShapesVec::const_iterator it = other.shapes.begin(); 
        for ( ;it != other.shapes.end(); ++it) 
        { 
          temp.add((*it)->clone()); 
        } 
        shapes.swap(temp.shapes); 
    } 

    ~Shapes() 
    { 
         std::for_each(shapes.begin(), shapes.end(), delete_f()); 
    } 

    void add(Shape* p)
    { 
        std::auto_ptr<Shape> sp(p); 
        shapes.push_back(sp.get()); 
        sp.release(); 
    }
};
Sample of use

Shapes shapes;
shapes.add(new Circle); 

Shared_ptr pointer implementation of Shapes

class Shapes 
{
    typedef vector< std::tr1::shared_ptr<Shape> > ShapesVec;
    ShapesVec shapes;

public:
    
    Shapes()
    {
    }

    Shapes(const Shapes &other)
    {
        ShapesVec::const_iterator it = other.shapes.begin();
        for ( ;it != other.shapes.end(); ++it)
        {
          add(std::tr1::shared_ptr<Shape>((*it)->clone()));
        }        
    }

    ~Shapes()
    {
    }

    void add(std::tr1::shared_ptr<Shape> p)
    {
        shapes.push_back(p);
    }
};
Sample of use

Shapes shapes;
shapes.add(std::tr1::shared_ptr<Shape>(new Circle));
The copy constructor has been implemented in this sample. However, in many cases it is not necessary. Comparing the implementations we can notice that only few lines are required to make the raw pointer container safe. The destructor:

~Shapes() 
{ 
  std::for_each(shapes.begin(), shapes.end(), delete_f()); //extra
}
and the add function:

void add(Shape* p)
{ 
    std::auto_ptr<Shape> sp(p);  //extra
    shapes.push_back(sp.get()); 
    sp.release();  //extra
}
or

void add(std::auto_ptr<Shape> &sp)
{
    shapes.push_back(sp.get());
    sp.release(); //extra
}

Want to see more? Go to the CodeBlog section.

About the author: I am Thiago Adams. I work as a professional C++ software engineer. I have created this website to share ideas and source code with other people with similar interests.
I would like to hear from you comments, critics, questions and suggestions about this topic or any other part of this website. Email: thiago.adams at gmail dot com