4

This example was taken from Bruce Eckel's "Thinking in C++" Chapter 14, Section "Upcasting and the Copy Constructor".

#include <iostream>
using namespace std;

class Parent
{
    int i;

    public:
    Parent(int ii) : i(ii) { cout << "Parent(int ii)\n"; }
    Parent(const Parent& b) : i(b.i) {  cout << "Parent(const Parent&)\n"; }
    Parent() : i(0) { cout << "Parent()\n"; }
    friend ostream& operator<<(ostream& os, const Parent& b)
            { return os << "Parent: " << b.i << endl; }
};

class Member
{
    int i;

    public:
    Member(int ii) : i(ii) { cout << "Member(int ii)\n"; }
    Member(const Member& m) : i(m.i) { cout << "Member(const Member&)\n"; }
    friend ostream& operator<<(ostream& os, const Member& m)
            { return os << "Member: " << m.i << endl; }
};

class Child : public Parent
{
    int i;
    Member m;

    public:
    Child(int ii) : Parent(ii), i(ii), m(ii) { cout << "Child(int ii)\n"; }
    friend ostream& operator<<(ostream& os, const Child& c)
            { return os << (Parent&)c << c.m << "Child: " << c.i << endl; }
};

int main() {
  Child c(2);
  cout << "calling copy-constructor: " << endl;
  Child c2 = c;
  cout << "values in c2:\n" << c2;
}

The author makes the following comment regarding this code :

"The operator<< for Child is interesting because of the way that it calls the operator<< for the Parent part within it : by casting the Child object to a Parent& (if you cast to a base-class object instead of a reference you will usually get undesirable results):

return os << (Parent&)c << c.m << "Child: " << c.i << endl;

I also run the program, by replacing the above instruction by :

return os << (Parent)c << c.m << "Child: " << c.i << endl;

and the propram runs without a problem, with just one expected difference. Now the Parent copy constructor is called again to copy the argument c to the Parent::operator<<().

What are then, the undesirable results the author is talking about ?

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
Belloc
  • 6,318
  • 3
  • 22
  • 52

2 Answers2

2

The problem is, when you cast what is a Child hard to a Parent (and not a Parent&) you will simply slice off everything that makes Child a Child.

Usually, when your classes have virtual functions (and the normally have within class hierarchies) you can and will (depending on the internal layout, number of inherited classes etc.) modify the vptr and then you go all the way down towards the realm of undefined behavior. I.e. not using references (or pointers) in class hierarchies effectively kills all the magic inheritance mechanisms (also called polymorphism).

It is a bit like saying dog = plane; - and by using a reinterpret cast (which is what a C style cast effectively is) you take any chance from the compiler to warn you about that, because you are telling it to shut up.

cli_hlt
  • 7,072
  • 2
  • 26
  • 22
  • But there is no virtual function in this case – Belloc Feb 10 '12 at 12:52
  • Sure but then you might ask, why at all is there a hierarchy? Exactly, because this is **demo code**. If you get used to "i can reinterpret_cast whenever there is no virtual function" in production code you will not be the first whose code will break terribly and cause a whole lot of headaches because maintenance or redesign required the addition of a virtual method but your cast got away unnoticed. Besides that, you asked what the author meant, and that is what he meant. – cli_hlt Feb 10 '12 at 12:58
  • You may be right, but the chapter about virtual inheritance comes later in his book. – Belloc Feb 10 '12 at 13:01
  • This should not prevent a serious author from pointing out do's and especially **do not's** whenever appropriate. And this is exactly a case where it is appropriate, because anyone learning C++ would ask "Why can't I simply cast to parent?". And the author says: "Don't do it, because it is wrong.". The only thing he forgot is to write "Details follow later.". – cli_hlt Feb 10 '12 at 13:03
1

A bit of a tangent...

Rule of Thumb: A base class should not be Copyable, instead it should be Clonable.

Implementation: Either disable the Copy Constructor and Copy Assignment Operator or (simply) create a pure virtual method.

Relaxation: In the absence of a pure virtual method, it is easier to make the base class copy constructor and assignment operator protected. Warning: this means that the child class now has the ability to invoke the copy of its parent, which may trigger slicing issues.

Note: With C++11, this also apply to the move counterparts.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722