In C++, it is possible to assign or copy a derived class object to the base class object (the other way around is not possible, though). But when you do that, you invariably run into something called object slicing. It means that when an object of a derived class is assigned or copied to the base class object, then the derived portion of it is cut off or sliced, and only the base portion is assigned to the base object. Thus the derived portion is never known to the base object. It is a very common glitch that many C++ programmers try to avoid and one can find discussions on it on the Internet.
The following example shows object slicing caused by copying a derived class object to the base class object (while using the pass-by-value):
#include <iostream> using namespace std; class Base { public: Base(int a) { a_ = a; } void print() { cout<< "In Base::print() : a_ " << a_ <<endl; } private: int a_; }; class Derived : public Base { public: Derived(int a, int b):Base(a) { b_ = b; } void print() { cout<< "In Derived::print() : b_ " << b_ <<endl; } private: int b_; }; voiddisp (Base ob) { ob.print(); } int main() { Base b(10); Derived d(15, 25); disp(b); disp(d); // slicing will happen return 0; } ----- output ----- In Base::print() : a_ 10 In Base::print() : a_ 15 <-- Due to slicing
The example below demonstrates object slicing when a derived class object is assigned to the base class object:
#include <iostream> using namespace std; class Base { public: Base(int a) { a_ = a; } virtual void print() { cout<< "In Base: a_ = " << a_ <<endl; } private: int a_; }; class Derived : public Base { public: Derived(int a, int b):Base(a) { b_ = b; } void print() { cout<< "In Derived: b_ = " << b_ <<endl; } private: int b_; }; int main() { Base b(10); Derived d(15, 25); b.print(); d.print(); b = d; // Assign Derived class object to Base class object. b.print(); d.print(); return 0; } ----- output ----- In Base: a_ = 10 In Derived: b_ = 25 In Base: a_ = 15 <-- This is due to slicing! In Derived: b_ = 25
Of course, we did not define the assignment operator for the base class (which, by the way, is not needed in this case, as the member a_ is an int’ and the compiler performed assignment is good enough), but even that wouldnt have helped here. This is because the object dgets sliced when the assignment operator is invoked! Remember, operator overloading is actually a function call. Therefore, the object d, if you realise, will be passed as a function argument. The assignment operator (function) takes this passed object of the derived class as a base class object and so the derived portion is lost while copying.
One could argue that this behaviour seems fair and reasonable by all means. A base class object is equipped only to handle the base class entity and so, if someone tries to assign a bigger (yes, the derived object is usually bigger than the base class object) object to it, then obviously, the extra things will be deprecated or truncated. While the rationale behind object slicing seems obvious and easy to understand, its implications can be surprising.
It can be very tricky, if used unknowingly, especially when passing objects to functions, returning values from functions, assigning one object to another or creating (or copying) objects from another object. One should be very careful in such instances, during which you are bound to encounter object slicing.
Now lets look at one scenario which can throw up some surprises.
Lets modify the base class to have member a_ declared as protected (so that it is accessible by the derived class) and lets also modify the Derived::print() method to also display the base class member a_.
#include <iostream> using namespace std; class Base { public: Base(int a) { a_ = a; } void print() { cout<< In Base: a_ = << a_ <<endl; } protected: int a_; // Declared as protected. }; class Derived : public Base { public: Derived(int a, int b):Base(a) { b_ = b; } void print() { cout<< In Derived: a_ = << a_ << and b_ = << b_ <<endl; // It can access a_ as it was declared protected. } private: int b_; }; int main() { Base b(10); Derived d(11, 21), dd(15, 25); b.print(); d.print(); Base &bb = d; // take it by reference object of Base bb.print(); // Since print() is not defined virtual, it would call // Base::print() as bb is declared as Base reference and the // static binding will force it that way. bb = dd; // troublesome assignment! bb.print(); return 0; }
The output wouldnt be much different than what we would assume.
------ output ------ In Base: a_ = 10 In Derived: a_ = 11 and b_ = 21 In Base: a_ = 11 <-- Object d got sliced In Base: a_ = 15 <-- Object dd got sliced Now, lets declare Base::print() as virtual. class Base { virtual void print() { cout<< In Base: a_ = << a_ <<endl; } }
Lets run the same main program again. This time, the bb.print() call will invoke Derived::print() as the print() method is declared virtual in base. Now, notice the output carefully:
In Base: a_ = 10 In Derived: a_ = 11 and b_ = 21 In Derived: a_ = 11 and b_ = 21 In Derived: a_ = 15 and b_ = 21<-- there was never an object with (15, 21)!
What essentially has happened is that the object reference bb has some portion of object d and some portion of object dd. This happened because bb = dd made the derived part get sliced and therefore bb never got the value b_ = 25. So it retained the old value of b_ as 21 (from the initialisation) and used the new value of a_ as 15.
As can be seen, this subtle effect of object slicing can throw your program and understanding completely off course.
How to prevent it
A very simple but harsh solution is to make the base class abstract and disallow object creation. But this solution might not be applicable in many instances where one has a need to instantiate the base class or when one has multi-level inheritance needs.
An alternate solution
Here one needs to declare assignment operator as virtual in the base class. This way the invocation will know which exact operator (base or derived) to call at runtime. You also need to define the overridden assignment operator in the derived class to accept the base class object as the parameter. After all, thats what you intend to do (in the troublesome assignment mentioned above). We then need to define a copy() method, which will copy the members appropriately. The derived classs assignment operator needs to downcast the given base object and call these copy() methods appropriately.
Remember, this approach has to be implemented for all derived classes. You can also choose to define the copy() method as protected if you want to avoid direct invocation of it.
The following complete example shows how this works.
#include <iostream> using namespace std; class Base { public: Base(int a) { a_ = a; } virtual Base & operator = (const Base &ob) { copy (ob); return *this; } virtual void print() { cout<< In Base: a_ = << a_ <<endl; } void copy (const Base &other) { this->a_ = other.a_; } protected: int a_; }; class Derived : public Base { public: Derived(int a, int b):Base(a) { b_ = b; } virtual Derived & operator = (const Base &base) { if (const Derived *der = dynamic_cast<const Derived *> (&base)) { copy (*der); // calling Derived::copy() return *this; } else { // the cast could not succeed. cerr<< Error! Wrong cast invoked.; exit (1); } } void print() { cout<< In Derived: a_ = << a_ << and b_ = << b_ <<endl; } void copy (const Derived &other) { Base::copy(other); // Let Base::copy() handle copying Base things this->b_ = other.b_; } private: int b_; }; int main() { Base b(10); Derived d(11, 21), dd(15, 25); b.print(); d.print(); Base &bb = d; // Assign Derived class object to Base class object. bb = dd; bb.print(); return 0; }
Lets look at the output now.
------ output ------ In Base: a_ = 10 In Derived: a_ = 11 and b_ = 21 In Derived: a_ = 15 and b_ = 25<-- After correct copying
References
[1] https://msdn.microsoft.com
[2] http://stackoverflow.com
[3] http://cppreference.com
Thanks for the nice article. Just one doubt about the below line…
bb = dd; // troublesome assignment!
Even though bb is reference, why is object slicing still happening?