Freeman
Freeman

Reputation: 5

C++ Confused by this code with polymorphism, pointers and object slicing

I'm trying to understand how polymorphism, object slicing and pointers work in this block of code. I'm working in Visual Studio.

#include <iostream>

class Man {
public:

    virtual void speak() { std::cout << "I'm a man." << "\n"; }

private:

};

class Soldier : public Man {
public:

    virtual void speak() { std::cout << "I'm a soldier." << "\n"; }

private:

};

int main() {

    Man man1;
    Soldier soldier1;

    man1 = soldier1;
    std::cout << "Man1: "; man1.speak();

    Man *man2 = new Man;
    Man *soldier2 = new Soldier;

    man2 = soldier2;
    std::cout << "Man2: "; (*man2).speak();

    Man *man3 = new Man;
    Soldier *soldier3 = new Soldier; // "Man *soldier3 = new Soldier;" will give the same output.

    *man3 = *soldier3;
    std::cout << "Man3: "; man3->speak();

    return 0;

}

The output is:

Man1: I'm a man.
Man2: I'm a soldier.
Man3: I'm a man.

I did some searching and learned about the concept "object slicing". I guess that is what has happened to man1 and man3. But wait, didn't we use the keyword "virtual" in all of those classes? Shouldn't man1 and *man3 first find out what class of object they each are, and call the specific overriding speak()?

Or is it because slicing has already happened at the = operator, at the line:

man1 = soldier1;

And the line:

*man3 = *soldier3;

And man1 and *man3 are really just Man objects now?

A fellow coder guessed it is because the = operator is only assigning the right-hand-side value to a copy of the left-hand-side variable. It needs to be confirmed still.

Aside from those, the goal I want to achieve is to copy that Soldier object to a different memory address, unlike how I point two pointers at one same memory address, in the case of man2 and soldier2.

Finally, I wonder why slicing doesn't happen in part2, and what really happens at syntax like this:

Man *soldier2 = new Soldier;

Seriously what does it do..?

I appreciate any insight on this. I'm a basic C++ programmer :) <

Upvotes: 0

Views: 636

Answers (3)

n. m. could be an AI
n. m. could be an AI

Reputation: 119887

didn't we use the keyword "virtual" in all of those classes

The virtual keyword is unrelated to object slicing.

Shouldn't man1 and *man3 first find out what class of object they each are, and call the specific overriding speak()?

No, they cannot and they should not. C++ has a certain object model. It includes objects occupying a certain region of memory. There's no place in this model for object assignment working like you expected it to work.

Consider this modification to a soldier class:

class Soldier : public Man {
public:

    virtual void speak() { std::cout << "I'm a soldier with a" << weapon << "\n"; }

    Soldier (const std::string& w) : weapon(w) {}

private:

    std::string weapon;

};

Now you have Soldier objects occupying more space than Man objects. When you assign

man1 = soldier1

there's simply no place inside man1 to fit the weapon string, so it gets cut off. Now soldier's speak cannot possibly work because it would not be able to find the weapon, so man's speak is used.

When you assign pointers, there's no cut off because a Man pointer is perfectly capable of pointing to a Soldier. At the raw memory level all pointers are essentially the same and they don't care what to point at. This is why polymorphism can work.

Some (including myself) would argue that since object slicing is almost always an error, an attempt to invoke it should cause a compile-time error rather than confusing silent breakage. But the language isn't currently defined this way, so you have to watch out.

Upvotes: 3

jhauris
jhauris

Reputation: 1143

Or is it because slicing has already happened at the = operator, at the line: ...

There is indeed slicing at man1 = soldier1, but this does not affect the man3/soldier3 case. Slicing in this case is exactly the same as when you dereference the pointers with man3 / soldier3. See below.

When you say man2 = soldier2 you are saying "set the address stored in the pointer man1 to the address stored in soldier1". This does NOT cause address slicing, the memory location pointed to by man1 is now a soldier.

HOWEVER, when you say *man3 = *soldier3 you are telling the computer to assign the value of man3 in memory to the value stored in soldier3. In other words, you are saying "Take the value stored in the memory location pointed to by soldier3, and store it in the memory location pointed to by man3." (By the way, this is called "dereferencing" the pointer). The problem is the memory needed to store a man is too small to hold a soldier, because a soldier is a man plus some other data. Therefore the compiler slices the new soldier data off before storing it in the man memory location.

Finally, I wonder why slicing doesn't happen in part2, and what really happens at syntax like this:

The reason this doesn't cause slicing basically is because C++ is designed to work this way with pointers. The actual size of all pointers is the same on a given architecture, and the compiler knows that a soldier "is a" man. Because you declared your function virtual, the compiler knows to use the correct override for that function.

Upvotes: 1

Oleg
Oleg

Reputation: 205

In two words: Soldier virtual member-functions are called only when you have an instance of Soldier class. You may access it via Man class pointer or reference pointing to Soldier object. When you assign or copy Soldier variable to Man variable, only base class Man is copied, because the target variable cannot accommodate Soldier class.

Upvotes: 0

Related Questions