Tuesday, September 23, 2008

virtual constructor

An idiom that allows you to do something that C++ doesn't directly support.

You can get the effect of a virtual constructor by a virtual clone() member function (for copy constructing), or a virtual create() member function (for the default constructor).

class Shape {
public:
virtual ~Shape() { }
// A virtual destructor
virtual void draw() = 0;
// A pure virtual function
virtual void move() = 0;
...
virtual Shape* clone() const = 0;
// Uses the copy constructor
virtual Shape* create() const = 0;
// Uses the default constructor
};

class Circle : public Shape {
public:
Circle* clone() const;
// Covariant Return Types; see below
Circle* create() const;
// Covariant Return Types; see below
...
};

Circle* Circle::clone() const { return new Circle(*this); }
Circle* Circle::create() const { return new Circle(); }

In the clone() member function, the new Circle(*this) code calls Circle's copy constructor to copy the state of this into the newly created Circle object. (Note: unless Circle is known to be final (AKA a leaf), you can reduce the chance of slicing by making its copy constructor protected.) In the create() member function, the new Circle() code calls Circle's default constructor.

Users use these as if they were "virtual constructors":

void userCode(Shape& s)
{
Shape* s2 = s.clone();
Shape* s3 = s.create();
...
delete s2;
// You need a virtual destructor here
delete s3;
}

This function will work correctly regardless of whether the Shape is a Circle, Square, or some other kind-of Shape that doesn't even exist yet.

Note: The return type of Circle's clone() member function is intentionally different from the return type of Shape's clone() member function. This is called Covariant Return Types, a feature that was not originally part of the language. If your compiler complains at the declaration of Circle* clone() const within class Circle (e.g., saying "The return type is different" or "The member function's type differs from the base class virtual function by return type alone"), you have an old compiler and you'll have to change the return type to Shape*.

Note: If you are using Microsoft Visual C++ 6.0, you need to change the return types in the derived classes to Shape*. This is because MS VC++ 6.0 does not support this feature of the language. Please do not write me about this; the above code is correct with respect to the C++ Standard (see 10.3p5); the problem is with MS VC++ 6.0. Fortunately covariant return types are properly supported by MS VC++ 7.0.

Reference:

http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.8


No comments: