Question

Weird behaviour of C++ pure virtual classes

Please help a bloody C++-beginner understand pure virtual classes better.

I tried a simple example with C++ virtuals and am not sure about the result. If I tried the same in another programming language as java for example the output would be

Desired/Expected Output

1 -> Tweet
2 -> Tweet

However, here the output is

Actual Output

1 -> Meow
2 -> Tweet

Why is that? It seems as if the operator= of the class animal had no effect. Is that because a standard operator= of the class animal is called which just does nothing? How would I achieve a behaviour similiar to java without having to use pointers? Is that even possible?

Below an as simplified as possible example of code:

Code

#include <string>
#include <iostream>

using namespace std;

class Animal
{
public:
    virtual string test() const = 0;
};

class Cat : public Animal
{
public:
    virtual string test() const
    {
        return "Meow";
    }
};

class Bird : public Animal
{
public:
    virtual string test() const
    {
        return "Tweet";
    }
};

void test_method(Animal &a)
{
    Bird b;
    a = b;
    cout << "1 -> " << a.test() << endl;
    cout << "2 -> " << b.test() << endl;
}

int main(int args, char** argv)
{
    Cat c;
    Animal &a = c;
    test_method(a);
    return 0;
}
Was it helpful?

Solution

Apparently, you have some unrealistic expectations about the behavior of a = b assignment.

Your a = b assignment simply copies data from Animal subobject of Bird object b to Animal subobject of Cat object referred by a. Since Animal has no data in it at all, you a = b is an empty operation, a no-op. It does not do anything at all.

But even if it did do something, it would simply copy the data fields. It cannot copy the polymorphic identity of the object. There no way to change the polymorphic identity of an object in C++. There's no way to change object's type. Regardless of what you do, a Cat will always remain a Cat.

The code you wrote can be shortened to

Cat a;
Bird b;

(Animal &) a = b; // Same as `(Animal &) a = (Animal &) b`

a.test(); // still a `Cat`
b.test(); // still a `Bird`

You did the same thing in a more obfuscated way.

OTHER TIPS

In C++ references can't be rebound; they always refer to the variable you created them against. In this case a is a reference to c, and thus it is and will always be a Cat.

When you reassign a reference, you aren't rebinding the reference - you're assigning the underlying variable that the reference refers to. Thus the assignment a = b is the same as c = b.

You are a victim of what is known as object slicing. C++ is different from Java and C# in the way it handle its objects. In C# for example every user define type is managed as a reference when you create a new instance, and only primitive types and structures are handled as values, This means that in Java or C# when you assign an object, you are only assigning references, for instance:

Object a = new Object(); Object b = a;

Will result in both a and b pointing to the same object (The one created when we assigned a).

In C++ the story is different. you can create an instance of an object in the heap or the stack. and you can pass said objects by reference, by pointer or by value.

If you asssign references or pointers, it will behave similar to C# and Java. But if you assign an object by value, that is you assign the actual object and not a pointer or a reference, a new copy of the object will be created. Every user define type in C++ is copyable by default.

When you have inheritance and polymorphism involved, this copy behaviour creates an issue, because when you copy a child type into a parent type, the copy that will be created will only contains the portion of information for the parent type in the child, thus losing any polymorphism you may have.

In your example when you copy a Cat object into a Animal object, only the Animal part of the cat is copied, thats why you lose your polymorphism, the virtual table is no more. If you base class is abstract in any way this wont even be possible.

The solution, if you want to retain polymorphism, is to pass the object by pointer or reference instead of by value. You can create the object in the heap and assign that pointer, you can take the address of the object in the stack and assing that pointer, or you could just take the reference of the object and assign that instead.

The lesson to be learned here is to NEVER pass or assign by value objects with any sort of polymorphism or you will end up slicing it.

Try this.

#include <string>
#include <iostream>

using namespace std;

class Animal
{
public:
    virtual string test() const = 0;
};

class Cat : public Animal
{
public:
    virtual string test() const
    {
        return "Meow";
    }
};

class Bird : public Animal
{
public:
    virtual string test() const
    {
        return "Tweet";
    }
};

void test_method(Animal *a)
{
    Bird *b = new Bird();
    a = b;
    cout << "1 -> " << a->test() << endl;
    cout << "2 -> " << b->test() << endl;
    free(a);
}

int main(int args, char** argv)
{
    Cat *c = new Cat();
    Animal *a = c;
    test_method(a);
    free(c);
    return 0;
}

Remove the a = b, and you'll get "Meow" followed by "Tweet"...

And in a bit more generic attitude:

First, define a generic interface for your generic class (Animal in this specific example).

You can then use this interface with any sub-class instance (Cat and Bird in this specific example).

Each instance will "act" according to your specific implementation.

Your mistake in function test_method was using an instance of the sub-class without referring to it through the generic class (with a reference or a pointer).

In order to change it into a generic function for Animal instances, you could do something like:

void test_method(Animal &a)
{
    cout << a.test() << endl;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top