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
Artjom B.
  • 61,146
  • 24
  • 125
  • 222
Manux
  • 3,643
  • 4
  • 30
  • 42
  • 2
    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? – AnT stands with Russia Apr 28 '11 at 15:36
  • @AndreyT this wasn't real code I just wrote two potential examples. – Manux Apr 28 '11 at 15:55
  • 2
    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. – AnT stands with Russia Apr 28 '11 at 18:32

8 Answers8

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.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    +1: The question if not 100% clear to me. But this is my conclusion as well. – Martin York Apr 28 '11 at 15:46
  • 2
    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]". – Manux Apr 28 '11 at 15:59
  • 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) – Riga Apr 28 '11 at 16:59
  • @Riga: By "achieve" I meant accessing `Texture` implementation using the pointer of type `Material*`. – Nawaz Apr 28 '11 at 17:02
  • @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. – Riga Apr 28 '11 at 17:13
  • @Riga: Did you read what I meant by "achieve runtime polymorphism"? – Nawaz Apr 28 '11 at 17:25
  • @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. – Riga Apr 28 '11 at 17:45
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.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • 2
    Despite the `=` sign, there is no assignment in `Type foo = bar;` – fredoverflow Apr 28 '11 at 15:32
  • @FredOverflow: you're right, fixed that. I thought there would be if `Material` has no copy constructor. – Fred Foo Apr 28 '11 at 15:33
  • 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 &)` – Mihran Hovsepyan Apr 28 '11 at 15:39
  • @Mihran Hovsepyan: No. There is never assignemnt here. Try it. – Martin York Apr 28 '11 at 15:48
  • @Mihran: actually it doesn't. Try defining `Material` with public default ctor and `operator=` but private copy ctor. – Fred Foo Apr 28 '11 at 15:48
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.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • 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). – Martin York Apr 28 '11 at 15:46
  • 1
    @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. – AnT stands with Russia Apr 28 '11 at 16:16
  • 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. – Martin York Apr 28 '11 at 18:06
  • 1
    @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. – AnT stands with Russia Apr 28 '11 at 18:31
  • @ 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. – Martin York Apr 29 '11 at 14:22
  • 1
    @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. – AnT stands with Russia Apr 29 '11 at 18:19
  • @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. – Martin York Apr 30 '11 at 00:41
2

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

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
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
Peter G.
  • 14,786
  • 7
  • 57
  • 75
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.

Erik
  • 88,732
  • 13
  • 198
  • 189
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.

Alok Save
  • 202,538
  • 53
  • 430
  • 533
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.

sht
  • 1
  • 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. – YurkoFlisk Sep 11 '22 at 05:25