Why do virtual functions need to be passed with a pointer and not by value(of the object)?
Asked Answered
I

8

8

I think I understand the concept of virtual methods and vtables, but I don't understand why there is a difference between passing the object as a pointer(or reference) and passing it by value (which kind of scraps the vtable or something?)

Why would something like this work:

Material* m = new Texture;
poly->setMaterial(m); 
// methods from Texture are called if I keep carrying the pointer around

And not this?:

Material m = Texture();
poly->setMaterial(m);
// methods from Material are called if I pass the value around
Ignace answered 28/4, 2011 at 15:28 Comment(3)
What is poly and what does it have to do with anything? Where are your actual virtual calls? How come your setMaterial method can accept both pointers and values? Is it overloaded?Syndic
@AndreyT this wasn't real code I just wrote two potential examples.Ignace
Great, but your question is about virtual calls. Yet your examples (no matter real or artificial) do not include any relevant virtual calls at all.Syndic
H
20

Because if you pass by value, then object slicing will occur, and runtime polymorphism cannot be achieved. And in your code, the very line Material m = Texture() causes object slicing. So even if you pass m by pointer (or reference), runtime polymorphism cannot be achieved.

Also, runtime polymorphism is achieved through:

  • pointer of base type, or
  • reference of base type

So if you want runtime polymorphism, you've use either pointer or reference of base type, and here are few examples how you can achieve runtime polymorphism:

Material* m1 = new Texture();
poly->setMaterial(m1);     //achieved

Texture* t1= new Texture();
poly->setMaterial(t1);     //achieved

Texture t2;
poly->setMaterial( &t2);   //achieved : notice '&'

Material & m2 =  t2;
poly->setMaterial( &m2 );  //achieved : notice '&'

Material  m3;
poly->setMaterial( &m3 );  //NOT achieved : notice '&'

Only in the last line you don't achieve runtime polymorphism.

Hannus answered 28/4, 2011 at 15:31 Comment(7)
+1: The question if not 100% clear to me. But this is my conclusion as well.Waxplant
Thanks, that's probably what was happening in my code. The second sentence of the wiki article quite was the revelation: "since the superclass has no place to store them [the child's attributes]".Ignace
I don't understand your statement. What do you mean by writing "achieve"? setMaterial will work for all Material hierarchy due to subtype polymorphism. Polymorphism is a property of setMaterial, it is better to say "employed". If we say that polymorphism here is a ability to work with various types that have Material interface, then every getMaterial invoke will use polymorphism (since Material class itself implements his interface)Fridell
@Riga: By "achieve" I meant accessing Texture implementation using the pointer of type Material*.Hannus
@Namaz, no, you wrote "achieve runtime polymorphism", your last statement "not achieved" is not correct because polymorphism is achieved by taking an object that implements Material interface every time. As you have stated previously, function gets not an object itself, but a pointer or a reference. It works with this pointer successfully, polymorphism is in use.Fridell
@Riga: Did you read what I meant by "achieve runtime polymorphism"?Hannus
@Nawaz, oh! I see! bool operator==(const Obj& o1, const Obj& o2) { return o1 != o2; } It's OK! you just meant something different! Solid approach.Fridell
P
4

Material m = Texture() would call the constructor Material::Material(Texture const &), or, if that's not available, the Material copy constructor, which construct a Material rather than a Texture.

There is no way this can build a Texture object for you, so the object is sliced to a base class object.

Propensity answered 28/4, 2011 at 15:31 Comment(5)
Despite the = sign, there is no assignment in Type foo = bar;Lug
@FredOverflow: you're right, fixed that. I thought there would be if Material has no copy constructor.Propensity
Small correction: Material m = Texture() would call the constructor Material::Material(Texture const &) or defalut constructor Material::Material() then assignment operator Material::operator=(Texture const &)Muriate
@Mihran Hovsepyan: No. There is never assignemnt here. Try it.Waxplant
@Mihran: actually it doesn't. Try defining Material with public default ctor and operator= but private copy ctor.Propensity
S
3

Virtual functions works perfectly well in both of your examples. They work exactly as they are supposed to work.

The whole idea of a virtual function is that an call to such function is dispatched in accordance with the dynamic type of the object used in the call. (Unfortunately, you didn't show in your examples how you make these calls.)

In your first example, you created an object of type Texture. The dynamic type of the object is Texture, so the virtual calls go to the methods of Texture.

In the second case you create an object of type Material. The dynamic type of the object is Material, so the virtual calls go to the methods of Material.

That's all there is to it. Everything works just as one would expect. If your expectations are different from this, then you should just bring them in better alignment with the language.

Syndic answered 28/4, 2011 at 15:33 Comment(7)
I think the problem in the second example is that a texture will be sliced into a Material on copy construction. Thus thus any virtual functions called on m will not work (as a beginner expected).Waxplant
@Martin: No. The problem is that the object created in the second example has type Material. Period. "Slicing" in this case is completely beside the point. "Slicing" happens during the initialization of the new object. Initialization does not matter. The OP creates an object of type Material - that's the only thing that matters. How the OP is trying to initialize that object (with "slicing" or without) is totally irrelevant.Syndic
Totally disagree. They create an object of type Texture. By the time they call any virtual methods it is no longer a texture but a Material object (that is the crux of the problem). They get a Materiel because the texture was sliced. So the code is working as I would expect. The problem is that it is not working as the OP expects because their object was sliced.Waxplant
@Martin: Irrelevant. The second code creates a named object m of type Material and a temporary object of type Texture. The virtual calls are performed through the object m of type Material. The temporary of type Texture does not in any way participate in virtual calls (it simply can't), which is why it is totally irrelevant. The calls, once again, are made through m, which is Material. That's the only thing that matters. The fact that this m was initialized with Texture() does not matter at all. The type of the object is what matters. How it was initialized is irrelevant.Syndic
@ AndreyT: It makes it irrelevant to the running system. But it is this exact fact that makes it the MOST relevant part part of the answer. In fact it is the answer. It is the ONLY part of the answer that really matters. Everything else is Irrelevant.Waxplant
@Martin: I disagree. This is actually the reason the OP ran into this confusion in the first place: the OP assumed that the temporary Texture object will somehow affect the dynamic behavior of the final Material object. In reality it doesn't. Slicing does indeed take place during the initialization of the Material object. However, since the focus of this question is the virtual behavior, it is most important to insist on what really matters: virtual behavior depends on the type of the object and on nothing else. Everything else is beside the point.Syndic
@AndretT: Yes its all about virtual behavior. The virtual behavior did not occur because of the slicing. So the error is the result of slicing that caused an unexpected type change. The slicing caused the error so the slicing is the point. If the OP does not understand that slicing occurred they will more than likely repeat the mistake.Waxplant
L
2

Because Material m = Texture(); slices the object - at this point, you just have a Material.

Lug answered 28/4, 2011 at 15:31 Comment(0)
R
1

Once you assign a Texture objecto to a Material, it is sliced to a Material. Therefore, any call on the m Object will dispatch only the Material functions.

Material m has space for exactly one Material object.

Using plain structs, I illustrate what is meant by slicing:

struct A { 
  int a;
};

struct B : public A {
  int b;
};

A objectA = B();
objectA.b = 1; // compile error, objectA does only have the properties of struct A
Romanticist answered 28/4, 2011 at 15:31 Comment(0)
A
1
Material m = Texture();

This create a temporary Texture, then creates a Material by copying the Material part of Texture. This is called slicing, and is typically not what you want.

Allegory answered 28/4, 2011 at 15:31 Comment(1)
It doesn't construct and then copy; it calls the copy constructor.Propensity
S
1
class Base
{
    //Members       
};

class Derived1:public Base
{
    //Members
};

int main()
{
    Base obj1;
    Derived1 obj2;

    obj1 = obj2;   //Allowed Since Public Inheritance 
}

When obj1 = obj2 only those members of the Derived Class obj2 that are inherited from Base Class get copied in to obj1, rest of the members of Derived Class get sliced off. This is simply because Base class obj1 is not aware of members of Derived class. This phenomemon is called Object Slicing.

In your case, when you call Material m = Texture() only contains members of Material and hence any function call on the object calls member functions of Material& not Texture.

Smearcase answered 28/4, 2011 at 15:38 Comment(0)
R
0

based on c++ primer P604, virtual function could be resolved on run time once call is made through reference or pointer.

in example code on book

Bulk_quote derived("xxx",50,5,.19);
print_total(cout, derived, 10);

print_total is declared as

double print_total(ostream &os, const Quote &item, size_t n){
/*definition*/
}

the item here is passed through reference so it still calls the virtual function in derived class.

so in your case, I think if your setMaterial function get a Material reference parameter, it could still call Texture virtual function.

Recondite answered 6/9, 2022 at 4:37 Comment(1)
Yes, but the question is about what happens when passing a derived class object by value as base class parameter. Then object slicing occurs, as is explained in the accepted answer.Latrice

© 2022 - 2024 — McMap. All rights reserved.