4

If I remember correctly, in Java, we can pass a subclass to a function with a superclass. The code would look like this.

// Assume the classes were already defined, and Apple
// and Pineapple are derived from Fruit.
Fruit apple = new Apple();
Fruit pineapple = new Pineapple();

public void iHaveAPenIHaveAn(Fruit fruit) { ... } // :)
...
public static void main(String[] arg)
{
    iHaveAPenIHaveAn(apple); // Uh! Apple-pen.
    iHaveAPenIHaveAn(pineapple); // Uh! Pineapple-pen.
}

However, in C++, I noticed from here that you need to use a reference variable of the base class (Is that the proper term?) instead of a regular variable of the base class.

Assuming you have two classes: a base class A, and an A-derived class B.

class A { ... };
class B : A { ... };

If we have a neverGonna() function that takes in a class A argument, then why should the function look like this:

void neverGonna(A& a) { ... }
...
B giveYouUp;
neverGonna(giveYouUp);

instead of this?

void neverGonna(A a) { ... }
...
B letYouDown;
neverGonna(letYouDown);

What is the reasoning behind it?

Community
  • 1
  • 1
Sean Francis N. Ballais
  • 2,338
  • 2
  • 24
  • 42

4 Answers4

5

Objects in Java are referenced by pointers. (Java calls them "references".) If you write Apple a; in Java, the variable a is actually a pointer, pointing to an Apple object somewhere in memory. In C++ though, if you write Apple a; then the variable a contains the entire Apple object. To get pointers, you need to explicitly declare a to be a pointer variable, as in Apple* a;, or a reference, as in Apple& a;.

The same goes for function arguments. In Java, if you send an Apple to a method that expects a Fruit (assuming that Apple is a subclass of Fruit), what is actually sent is a pointer, and the object itself is stored somewhere else.

In C++, the object itself is copied, and sent to the function. However, if the function expects a Fruit object, and you send an Apple object with some extra member variables in it, the Apple won't fit in the space that the function has to receive the Fruit. The Apple object has to be converted to a Fruit object, and any apple-specific extra stuff is removed. This is called object slicing.

Community
  • 1
  • 1
Thomas Padron-McCarthy
  • 27,232
  • 8
  • 51
  • 75
4

The reason is that sizeof(A) and sizeof(B) are not the same. If a function (in C++ or the like) takes a parameter by value, it must know how large the value for that parameter will be in order to interpret the values in memory correctly. As a rough (but technically sketchy) example, suppose I have a function that takes an A and an int. Maybe it expects the incoming values to be stored like

AAAAAAiiii

where the first 6 bytes are the A object, and the last 4 bytes are the integer value. But I create a B, which looks something like AAAAAABBBBB... so now the function receives

AAAAAABBBBBiiii

and that's no good. Passing either a pointer or a reference allows the function to know how many of the bytes its receiving represent that first parameter.

So why isn't this a thing in Java? In java objects are always handled "by reference"; that is, when you say

Fruit apple = new Apple();

you're creating a variable apple which is a reference to a Fruit. If a java method says

public void iHaveAPenIHaveA(Fruit fruit)

it's going to accept a reference to a fruit.

In other words, what your Java sample is doing actually is the same thing as the by-reference version of your C++ sample (the one with void neverGonna(A& a)).

Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52
  • So basically, passing by value would cut off the non-`A` data from `B` (which, as others have mentioned, is called object slicing) while passing by reference would allow the non-`A` data to pass "through." Am I right? Also, how does a function how know many bytes its receiving represents class `A` when you are passing by reference? – Sean Francis N. Ballais Jan 12 '17 at 08:18
  • 1
    When you pass by reference, the function only needs to know how many bytes make up the reference in order to receive it properly. Syntactic sugar aside, a reference is internally not much different from a pointer, so a reference-to-`A` is the same size as a reference-to-`B`. The receiving function still only knows the layout of the `A` part of the `B` it received, but because of built-in polymorphism (i.e. the vtable for virtual functions) that's all it needs as long as the value wasn't sliced. – Mark Adelsberger Jan 14 '17 at 18:14
  • If you pass by reference, would object slicing still happen or not? From how I understand from the comment above, I think it will still happen since the function only knows about the layout of `A` and not the extra data in `B`. – Sean Francis N. Ballais Jan 16 '17 at 05:22
  • No, as pointed out above, object slicing will not occur when passing by reference because the function can understand the incoming arguments without it. – Mark Adelsberger Jan 17 '17 at 14:07
  • So the function just needs to reserve memory for the reference? – Sean Francis N. Ballais Jan 18 '17 at 02:43
3

If you pass the object by value, then a (temporary) copy of the object is pushed into the stack before the function is called.

Since the function prototype dictates that the input argument's type is the base class, that copy contains only the "base" part of the object.

In other words, the original object is "sliced" and your hope for polymorphism goes to waste...

barak manos
  • 29,648
  • 10
  • 62
  • 114
2

Because passing by value will cause object slicing. Consider this example:

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void foo()
    {
        cout << "Base foo()" << endl;
    }
};

class Derived : public Base
{
public:
    void foo()
    {
        cout << "Derived foo()" << endl;
    }   
};

void print(Base b)
{
    b.foo(); // object will be sliced here
}

int main()
{
    Base* b = new Derived();
    print(*b); // passing by value will cause object slicing;
    return 0;
}

Since we passed by value b in print(), there would be object slicing and the non-Base data in b will be cut off. The output would look like:

Base foo()

instead of:

Derived foo()
Sean Francis N. Ballais
  • 2,338
  • 2
  • 24
  • 42
Raindrop7
  • 3,889
  • 3
  • 16
  • 27