In the following code, why does the last call of eat() on the reference c return "An animal b is eating." ? From my understanding, c is a reference to instance b of the derived class Dog and eat() is a virtual function. So it should have returned "A dog b is eating."
#include <string>
#include <iostream>
using namespace std;
class Animal
{
protected:
string name;
public:
Animal( string _name ):
name(_name)
{
}
virtual void eat()
{
cout << "An animal " << name << " is eating." << endl;
}
};
class Dog : public Animal
{
public:
Dog( string _name ):
Animal(_name)
{
}
void eat()
{
cout << "A dog " << name << " is eating." << endl;
}
};
int main( int argc , char ** argv )
{
Animal a("A");
a.eat();
Dog b("b");
b.eat();
Animal & c = a;
c.eat();
c = b;
c.eat();
return 0;
}
This is the output:
An animal A is eating.
A dog b is eating.
An animal A is eating.
An animal b is eating.
In order to make use of the dynamic polymorphism provided by virtual functions (distinguishing between derived and base classes during runtime), you need to access the derived class object via the base class pointer or reference.
I've commented out your code where confusion might have taken place:
int main( int argc , char ** argv )
{
Animal a("A");
a.eat();
Dog b("b");
b.eat();
// Make a reference (alias) to Animal object and set it to the object a.
// From this point on, whenever you write c, think "a".
Animal & c = a;
// So, this is a.eat()
c.eat();
// This is a = b (Animal = Dog): DANGER! SLICING! Here, the assignment operator
// slices the derived object and only assigns the base object "part" (remember,
// read "a", where you see "c" in your code):
// a.operator=(const A& b)
c = b;
// a.eat() = a is object of type A, so naturally, here you call A::eat()
c.eat();
return 0;
}
Animal & c = a;
c.eat();
c = b; ///^^^
c.eat();
In C++, reference cannot be rebind to other object once it is being initialized. c
is still an alias of object a
, which is an Animal
, therefore, you saw the output which is expected.
A reference is an alias for an object. After you bind a reference to an object (and that must happen at initialization time), what you do on the reference is done on the object being referenced.
In particular, you cannot re-bind a reference that has been already bound to an object and let it reference a different object. Thus, the following assignment (because that's an assignment, not an initialization):
c = b;
Is equivalent to the following:
a = b;
Since c
is a reference to object a
. The above assignment results in slicing, which is not what you wanted: c
won't be a reference bound to b
, but it will still be a reference bound to a
, to which b
has been assigned.
Because you can't rebind references. Once you initialized them, their name always refers to the object you have initialized them with.
An object can have a name, e.g. Animal a("A");
creates an object of type Animal
and introduces a name a
which refers to this object.
References on the other hand introduce names without introducing objects (let's not consider temporaries):
Animal& c = a; // a new name `c` which refers to the same object as `a`
// another (evil) example:
Animal& c = *(new Animal("C")); // `new` introduces an object without name
// `c` now refers to this object
Concerning the assignment:
Animal & c = a;
// the name `c` is now equivalent to the name `a`
c = b; // equivalent to `a = b;`
This last assignment takes the object referred to by b
, and copies its sub-object of type Animal
to the object which c
refers to. As a
and c
are equivalent, that's the same object a
refers to. Therefore, a.name
is set to "B"
.
The virtual function call c.eat()
of course operates on an id-expression (c
) whose dynamic type is Animal
- the same type as a
- therefore, Animal::eat
is called instead of Dog::eat
.
You cannot re-bind a reference once you have bound it, so you have to use pointers instead of references:
Animal *c = &a;
c->eat();
c = &b;
c->eat();
Now it will work exactly as you have wished it.