"Virtual constructor" and slicing

B

Bart Simpson

Can anyone explain the concept of "slicing" with respect to the "virtual
constructor" idiom as explain at parashift ?



From parashift:

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.
 
A

Alf P. Steinbach

* Bart Simpson:
Can anyone explain the concept of "slicing" with respect to the "virtual
constructor" idiom as explain at parashift ?



From parashift:

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.

Slicing is when only the (or more precisely, a) base class part of an
object is copied.

It can happen with clone() and, interpreting the word slicing very
liberally, with create() when the functions aren't properly overridden
and reimplemented in a derived class.

Mostly that's boilerplate code that could in principle be generated
automatically, and in practice you can do that via macros (if you're not
afraid of name collisions and other issues with macros). But as far as
I know nobody's found of way of ensuring at compile time that a class
does override a concrete member function, and in turn imposes that
requirement on its derived classes. Or even without the latter feature.

However, while you can't (AFAIK) detect at compile time or run time
whether the proper clone() function is called, you can detect at run
time that it's a wrong one, e.g.

Circle* clone() const
{
if( typeid( *this ) != typeid( Circle ) ) { throw "Urgh"; }
return new Circle( *this );
}

It's better than nothing, but both the cloning business and e.g. the
visitor pattern shows that there is most likely a very basic and general
language feature missing, something that does away with manually
replicating boilerplate code in the classes.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,955
Messages
2,570,117
Members
46,705
Latest member
v_darius

Latest Threads

Top