Slicing in C++

Posted on August 8, 2005. Filed under: C/C++ | Tags: , |

Suppose that class D is derived from class C. We can think of D as class C with some extra data and methods. In terms of data, D has all the data that C has, and possible more. In terms of methods, D cannot hide any methods of C, and may have additional methods. In terms of existing methods of C, the only thing that D can do is to override them with its own versions.

If x is an object of class D, then we can slice x with respect to C, by throwing away all of the extensions that made x a D, and keeping only the C part. The result of the slicing is always an object of class C.

Design Principle: Slicing an object with respect to a parent class C should still produce a well-formed object of class C.

Usage Warning: Even though D is-a C, you must be careful. If you have a argument type that is a C and you supply a D it will be sliced if you are doing call by value, pointer, or reference. See the example below.

Note on virtual functions. Their signatures are used to identify which one to execute.

Watch out for the sliced = operator, it can make the lhs inconsistent. Also, the operator= is never virtual, it wouldn’t make sense. For example, suppose classes A, B are both subclasses of class C. Just because an A is a C, and a B is a C, it doesn’t mean you can assign a B object to an A object. Without run-time type information you cannot make a safe assignment.

Usage Warning: If you ever change the size of an object, it and all its base classes must be recompiled! Unless everything is polymorphic and virtual!

13.2 slice.h

 

#ifndef _SLICE_
#define _SLICE_
 
#include <iostream.h>
 
class C {
 
public:
    explicit C(int initial = 0): id(initial), d1(100+initial) {}
    C(const C& arg): id(1000+arg.id), d1(arg.d1) {}
    virtual ~C()    {
        cout << "object " << id << " dying as a C\n";
    }
 
 
    void operator=(const C& rhs) 
    {
        cout << "Object " << id << " doing C =\n";
        Assign(rhs); 
    }
 
    virtual void Assign(const C& rhs) 
    {
        cout << "Assign C\n";
        d1 = rhs.d1;
    }
 
    void Ident() 
    {
        cout << "Object " << id 
            << " thinks it is a C object, d1="
            << d1 << "\n";
    }
 
    virtual void VirtIdent() 
    {
        cout << "Object " << id << 
            " virtually thinks it is a C object, d1="
            << d1 << "\n";
    }
 
protected:
    int id;
    int d1;
};
 
class D: public C {
public:
 
    explicit D(int initial = 0): C(initial), d2(200+initial) {}
    D(const D& arg): C(arg.id), d2(arg.d2) {}
    ~D() 
    {
        cout << "object " << id << " dying as a D\n";
    }
 
    void operator=(const D& rhs) 
    {
        cout << "Object " << id << " doing D =\n";
        Assign(rhs);
    }
 
    void Assign(const D& rhs)
    {
        cout << "Assign in D\n";
        C::Assign(rhs);
        d2 = rhs.d2;
    }
 
    // This version of Ident overrides the parent's, 
    // if we are not sliced!
    void Ident() 
    {
        cout << "Object " << id << 
            " thinks it is a D object, d1=" 
            << d1 << " d2=" << d2 << "\n";
    }
 
    void VirtIdent() 
    {
        cout << "Object " << id << 
            " virtually thinks it is a D object, d1=" 
            << d1 << " d2=" << d2 << "\n";
    }
private:
    int d2;
};
#endi

 

13.3 demo.cpp

 

#include <iostream.h>
#include "slice.h"
 
void SliceIt1(C arg) {
    cout << "Inside SliceIt1: ";
    arg.Ident();
    arg.VirtIdent();
}
 
void SliceIt2(C* arg) {
    cout << "Inside SliceIt2: ";
    (*arg).Ident();
    (*arg).VirtIdent();
}
 
void SliceIt3(C& arg) {
    cout << "Inside SliceIt3: ";
    arg.Ident();
    arg.VirtIdent();
}
 
void CopyIt1(C& x, C& y) {
    cout << "Inside CopyIt1:\n";
    x.Ident(); x.VirtIdent();
    y.Ident(); y.VirtIdent();
    x = y;
    x.Ident(); x.VirtIdent();
}
 
int main(int argc, char* argv[])
{
    D x(1);
    C y(2);
    D z(3);
 
    x.Ident(); x.VirtIdent();
    y.Ident(); y.VirtIdent();
    z.Ident(); z.VirtIdent();
    cout << "\n";
 
    SliceIt1(x); 
    SliceIt1(y);
    cout << "\n";
 
    SliceIt2(&x);
    SliceIt2(&y);
    cout << "\n";
 
    SliceIt3(x);
    SliceIt3(y);
    cout << "\n";
 
    CopyIt1(x, y);  
    // state of x wrong now
    x.VirtIdent();
    x.Ident();
    cout << "\n";
 
    CopyIt1(x, z);
    z.VirtIdent();
    z.Ident();
    cout << "\n";
    return 0;
}

 

13.4 output.txt

 

Object 1 thinks it is a D object, d1=101 d2=201
Object 1 virtually thinks it is a D object, d1=101 d2=201
Object 2 thinks it is a C object, d1=102
Object 2 virtually thinks it is a C object, d1=102
Object 3 thinks it is a D object, d1=103 d2=203
Object 3 virtually thinks it is a D object, d1=103 d2=203
 
Inside SliceIt1: Object 1001 thinks it is a C object, d1=101
Object 1001 virtually thinks it is a C object, d1=101
object 1001 dying as a C
Inside SliceIt1: Object 1002 thinks it is a C object, d1=102
Object 1002 virtually thinks it is a C object, d1=102
object 1002 dying as a C
 
Inside SliceIt2: Object 1 thinks it is a C object, d1=101
Object 1 virtually thinks it is a D object, d1=101 d2=201
Inside SliceIt2: Object 2 thinks it is a C object, d1=102
Object 2 virtually thinks it is a C object, d1=102
 
Inside SliceIt3: Object 1 thinks it is a C object, d1=101
Object 1 virtually thinks it is a D object, d1=101 d2=201
Inside SliceIt3: Object 2 thinks it is a C object, d1=102
Object 2 virtually thinks it is a C object, d1=102
 
Inside CopyIt1:
Object 1 thinks it is a C object, d1=101
Object 1 virtually thinks it is a D object, d1=101 d2=201
Object 2 thinks it is a C object, d1=102
Object 2 virtually thinks it is a C object, d1=102
Object 1 doing C =
Assign C
Object 1 thinks it is a C object, d1=102
Object 1 virtually thinks it is a D object, d1=102 d2=201
Object 1 virtually thinks it is a D object, d1=102 d2=201
Object 1 thinks it is a D object, d1=102 d2=201
 
Inside CopyIt1:
Object 1 thinks it is a C object, d1=102
Object 1 virtually thinks it is a D object, d1=102 d2=201
Object 3 thinks it is a C object, d1=103
Object 3 virtually thinks it is a D object, d1=103 d2=203
Object 1 doing C =
Assign C
Object 1 thinks it is a C object, d1=103
Object 1 virtually thinks it is a D object, d1=103 d2=201
Object 3 virtually thinks it is a D object, d1=103 d2=203
Object 3 thinks it is a D object, d1=103 d2=203
 
object 3 dying as a D
object 3 dying as a C
object 2 dying as a C
object 1 dying as a D
object 1 dying as a C

}

Here is another example:

 
class Shape {
public:
	Shape() { };
	virtual void draw() {};
};

class Circle : public Shape {
private:
	int m_iRadius;
public:
	Circle(int iRadius){ m_iRadius = iRadius; }
	virtual void draw() { /*do drawing*/}
	int GetRadius(){ return m_iRadius; }
};

void funcx(Shape shape) { /*do something*/}

Now, what if we try this:

 
Circle circle(2);
funcx(circle); //Pass a Circle as parameter, when function expects a Shape

Will this result in an error? No. Because Circle is derived from Shape, the compiler will generate a default assignment operator and will copy all the fields common to Circle and Shape. But the m_iRadius field will be lost and there is no way it can be used in funcx.

Another problem is the loss of type information. This may result in undesirable behavior by virtual functions. This phenomenon is called slicing. It is common in exception handling, when exceptions are caught using base class. It is used either to avoid multiple catch blocks or when the types of possible thrown exception is unknown.

 
class CException {};
class CMemoryException : public CException {};
class CFileException: public CException{};

try{ /*do something silly*/ }
catch(CException exception) {/*handle exception*/}

To avoid slicing, change the operations so they use pointer or reference to the base class object rather then the base class object itself. In the given sample code, funcx can be changed so that it takes a pointer/reference to Shape as a parameter rather than Shape:

 
void funcx(Shape *shape) { /*do something*/}

Inside the function body, this pointer can be safely type-casted to a Circle pointer to access Circle specific information:

 
dynamic_cast(shape)->GetRadius();

Reference:
http://webdocs.cs.ualberta.ca/~hoover/Courses/201/201-New-Notes/lectures/section/slice.htm
http://www.devx.com/tips/Tip/14570
Advertisements

Make a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Liked it here?
Why not try sites on the blogroll...

%d bloggers like this: