Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

virtual constructor idiom with smart pointers

I've a hierarchy of polymorphic classes, say a Shape abstract base class together with its derived classes, e.g. Rectangle, Circle, etc. Following the Virtual Constructor Idiom, I was wondering why we need that the return types of the virtual constructor functions in the derived classes should return the same type as in its parent class when using smart pointers?
For example, see the code below, where the clone() and create() member functions need to return smart_pointers to the Shape class. However, when using simple pointers, the return types can be of the same type as the one of the derived classes.
Could anybody explain why we need to handle those functions in the referred way?

class Shape;

typedef std::unique_ptr<Shape> shape_ptr;

class Shape{

    public:

        //typedef std::unique_ptr<Shape> shape_ptr;

        Shape(){};
        virtual ~Shape(){};

        virtual void draw() const = 0;
        virtual float area() const = 0;

        virtual shape_ptr clone() const = 0;
        virtual shape_ptr create() const = 0;
        //virtual Shape*clone() const = 0;
        //virtual Shape*create() const = 0;
};

class Rectangle:public Shape{
    public:

        typedef std::unique_ptr<Rectangle> rectangle_SmartPtr;

        Rectangle(int height=0, int width=0):m_Height(height),m_Width(width){};
        Rectangle(const Rectangle & rect):m_Height(rect.m_Height),m_Width(rect.m_Width){};
        ~Rectangle(){};

        virtual void draw() const;
        virtual float area() const;

        //virtual rectangle_SmartPtr clone() const{ return rectangle_SmartPtr(new Rectangle(*this)); };
        // error C2555: 'Rectangle::clone': overriding virtual function return type differs and is not covariant from 'Shape::clone'
        //virtual rectangle_SmartPtr create() const{ return rectangle_SmartPtr(new Rectangle()); };
        // error C2555: 'Rectangle::create': overriding virtual function return type differs and is not covariant from 'Shape::create'

        virtual shape_ptr clone() const{ return shape_ptr(new Rectangle(*this)); }; //OK
        virtual shape_ptr create() const{ return shape_ptr(new Rectangle()); }; //OK

        //virtual Rectangle* clone() const{ return new Rectangle(*this); }; //OK
        //virtual Rectangle* create() const{ return new Rectangle(); }; //OK

    private:
        int m_Height;
        int m_Width;
};


class Circle:public Shape{
    public:

        typedef std::unique_ptr<Circle> circle_SmartPtr;

        Circle(float radius=0):m_Radius(radius){};
        Circle(const Circle & other):m_Radius(other.m_Radius){};
        ~Circle(){std::cout << "Circle destructor: " << this << std::endl; };

        virtual void draw() const;
        virtual float area() const;

        //virtual circle_SmartPtr clone() const{ return circle_SmartPtr(new Circle(*this)); };
        // error C2555: 'Circle::clone': overriding virtual function return type differs and is not covariant from 'Shape::clone'
        //virtual circle_SmartPtr create() const{ return circle_SmartPtr(new Circle()); }; 
        //  error C2555: 'Circle::create': overriding virtual function return type differs and is not covariant from 'Shape::create'

        virtual shape_ptr clone() const{ return shape_ptr(new Circle(*this)); }; //OK
        virtual shape_ptr create() const{ return shape_ptr(new Circle()); }; //OK

        //virtual Circle* clone() const{ return new Circle(*this); }; //OK
        //virtual Circle* create() const{ return new Circle(); }; //OK

    private:

        float m_Radius;
};
like image 292
Tin Avatar asked Jan 15 '12 14:01

Tin


2 Answers

This is called covariance.

In a class hierarchy, when the base class specifies a virtual method that return either a T* or a T&, then the derived classes are allowed to return U* or U&, respectively, providing that U derives from T (note: and obviously the const and volatile combinations).

It is a special rule, checked by the compiler, and it works because if U derives from T then a U* can be cast to a T*. Unfortunately the rule is limited in that it does not work for any conversion, and therefore even though you could normally construct a unique_ptr<Shape> from a unique_ptr<Rectangle>... covariance does not work.

This is why, in its Cloneable concept, Boost mandates to return a bare pointer type. It is a shame, but the only way to get covariance.

like image 132
Matthieu M. Avatar answered Sep 23 '22 04:09

Matthieu M.


When using raw pointers, the compiler allows for covariant return types but that's not possible when using smart pointers since unique_ptr< Rectangle > doesn't derive from unique_ptr< Shape >. The two classes are completely unrelated from the compiler's perspective.

like image 25
Andrew Durward Avatar answered Sep 23 '22 04:09

Andrew Durward