Why vptr is not static?
Asked Answered
D

8

16

Every class which contains one or more virtual function has a Vtable associated with it. A void pointer called vptr points to that vtable. Every object of that class contains that vptr which points to the same Vtable. Then why isn't vptr static ? Instead of associating the vptr with the object, why not associate it with the class ?

enter image description here

Daladier answered 17/12, 2012 at 12:26 Comment(10)
If it were made static... what would be the point of it?Estevez
How would you access it from an object than?Ailment
That would defeat the whole purpose. The clue is in the name: Virtual dispatch is dynamic, not static.Shrub
@martinho : Every object carries additional 4 bytes of memory when it can be just 4 byte for the whole class.Daladier
@Dima : an object can call static function as far as I know. ( yes, in .Net it isn't possible.) Moreover the compiler when replacing the code to fetch vptr can use Class name instead of object..Daladier
But how would the class be known at runtime? The compiler only knoes the base class, so it can only plug in the vptr of the base class.Ailment
@Dima : No, the compiler never plugs in the vptr of the base class. It goes to the memory location pointed to by the base class variable and fetches the derived class vptr out of it.Daladier
@HarshMaurya, exactly. So if the address is static (the vptr is not in the object) how will we know at runtime which vtable is associated with which object?Ailment
@DimaRudnik : right. Finally I got it :) But I wonder why this concept isn't mentioned anywhere. This can confuse the beginners.Daladier
"A void pointer called vptr points to that vtable." The vptr is not declared or accessible in C++. It does not have a C++ type, so it is not a void pointer. Even figuratively, its type is not void*: the compiler knows very well which kind of data structure the vptr points to: it points to the corresponding vtable.Jaquelinejaquelyn
B
10

The runtime class of the object is a property of the object itself. In effect, vptr represents the runtime class, and therefore can't be static. What it points to, however, can be shared by all instances of the same runtime class.

Bertold answered 17/12, 2012 at 12:28 Comment(6)
Agreed. Infact, it is also used in RTTI. But my point is : Since all the objects of a Class are of same type, the vptr will represent the same runtime class for each object. Why not create a static method getType() which uses static vptr to fetch its type. The object can call this static method to know their types.Daladier
@HarshMaurya And how would the compiler know which function to call? The whole point of using vptr is that the compiler doesn't know the actual type; that the actual type can vary at runtime.Humiliating
@HarshMaurya, if you have A& a what is the "runtime class" of that object? A? A1? A2? Something else? How do you know? Calling a static function of A can never tell you the runtime class (the correct term is the "dynamic type") of aHandstand
@HarshMaurya: That would create a chicken and egg problem. To call the getType function, you'd first need the virtual table pointer (since it would have to be virtual to work). But to get the virtual table pointer, you suggest we call getType. Oops.Rann
Got it. I am marking this as answer. Although comment of @DavidSchwartz was what made me understand the problem. Thank you.Daladier
@NPE, what is the type of vptr (its class type)? Imagine what will be the output if I do typeid(vptr).name() with respect to a class A and its object A AO?Toxicosis
H
8

Your diagram is wrong. There is not a single vtable, there is one vtable for each polymorphic type. The vptr for A points to the vtable for A, the vptr for A1 points to the vtable for A1 etc.

Given:

class A {
public:
  virtual void foo();
  virtual void bar();
};
class A1 : public A {
  virtual void foo();
};
class A2 : public A {
  virtual void foo();
};
class A3 : public A {
  virtual void bar();
  virtual void baz();
};

The vtable for A contains { &A::foo, &A::bar }
The vtable for A1 contains { &A1::foo, &A::bar }
The vtable for A2 contains { &A2::foo, &A::bar }
The vtable for A3 contains { &A::foo, &A3::bar, &A3::baz }

So when you call a.foo() the compiler follows the object's vptr to find the vtable then calls the first function in the vtable.

Suppose a compiler uses your idea, and we write:

A1 a1;
A2 a2;
A& a = (std::rand() % 2) ? a1 : a2;
a.foo();

The compiler looks in the base class A and finds the vptr for the class A which (according to your idea) is a static property of the type A not a member of the object that the reference a is bound to. Does that vptr point to the vtable for A, or A1 or A2 or something else? If it pointed to the vtable for A1 it would be wrong 50% of the time when a refers to a2, and vice versa.

Now suppose that we write:

A1 a1;
A2 a2;
A& a = a1;
A& aa = a2;
a.foo();
aa.foo();

a and aa are both references to A, but they need two different vptrs, one pointing to the vtable for A1 and one pointing to the vtable for A2. If the vptr is a static member of A how can it have two values at once? The only logical, consistent choice is that the static vptr of A points to the vtable for A.

But that means the call a.foo() calls A::foo() when it should call A1::foo(), and the call aa.foo() also calls A::foo() when it should call A2::foo().

Clearly your idea fails to implement the required semantics, proving that a compiler using your idea cannot be a C++ compiler. There is no way for the compiler to get the vtable for A1 from a without either knowing what the derived type is (which is impossible in general, the reference-to-base could have been returned from a function defined in a different library and could refer to a derived type that hasn't even been written yet!) or by having the vptr stored directly in the object.

The vptr must be different for a1 and a2, and must be accessible without knowing the dynamic type when accessing them through a poiner or reference to base, so that when you obtain the vptr through the reference to the base class, a, it still points to the right vtable, not the base class vtable. The most obvious way to do this is to store the vptr directly in the object. An alternative, more complicated solution would be to keep a map of object addresses to vptrs, e.g. something like std::map<void*, vtable*>, and find the vtable for a by looking up &a, but this still stores one vptr per object not one per type, and would require a lot more work (and dynamic allocation) to update the map every time polymorphic objects are created and destroyed, and would increase overall memory usage because the map structure would take up space. It's simpler just to embed the vptr in the objects themselves.

Handstand answered 17/12, 2012 at 14:10 Comment(3)
Sorry, but you misread the diagram. A1, A2 and A3 are not classes but objects of class A. Anyways, I got the solution. Thanks.Daladier
Oh, so there's no polymorphism in your diagram. Well maybe if you think about polymorphism, which is the reason for virtual functions to exist, you'll see why your idea doesn't work. If you don't have derived types then your idea would work, but if you don't have derived types then why would you use virtual functions in the first place?Handstand
This is the most clear explanation on this topic I have seen.Uncinus
G
1

The virtual table (which is, by the way, an implementation mechanism not mentioned in the C++ standard) is used to identify the dynamic type of an object at runtime. Therefore, the object itself must hold a pointer to it. If it was static, then only the static type could be identified by it and it would be useless.

If you are thinking of somehow using typeid() internally to identify the dynamic type and then call the static pointer with it, be aware that typeid() only returns the dynamic type for objects belonging to types with virtual functions; otherwise it just returns the static type (§ 5.2.8 in the current C++ standard). Yes, this means that it works the other way around: typeid() typically uses the virtual pointer to identify the dynamic type.

Gallows answered 17/12, 2012 at 12:35 Comment(2)
Where is typeof defined and where does it say it can only be used for polymorphic types? That's not true for GCC's typeof which works like decltype and can be used for non-polymorphic types, it just tells you the static type (which is obviously useless for trying to find the dynamic type.)Handstand
@JonathanWakely Sorry, that was a typo: I meant typeid. And I have just seen that the C++11 standard accepts using it on a non-polymorphic type: in this case, instead of throwing an exception, it just returns the static type. Which for our purposes amounts to the same: you cannot find the dynamic type unless you have a virtual pointer. I am updating my answer.Gallows
U
1

As everyone attest Vptr is a property of an object. Lets see why?

Assume we have three objects Class Base{ virtual ~Base(); //Class Definition }; Class Derived: public Base{ //Class Definition }; Class Client: public Derived{ //Class Definition };

holding relation Base<---Derived<----Client. Client Class is derived from Derived Class which is in turn derived from Base

Base * Ob = new Base; Derived * Od = new Derived; Client* Oc = new Client;

Whenever Oc is destructed it should destruct base part, derived part and then client part of the data. To aid in this sequence Base destructor should be virtual and object Oc's destructor is pointing to Client's destructor. When object Oc's base destructor is virtual compiler adds code to destructor of object Oc to call derived's destructor and derived destructor to call base's destructor. This chaining sees all the base, derived and client data is destructed when Client object is destroyed.

If that vptr is static then Oc's vtable entry will still be pointing to Base's destructor and only base part of Oc is destroyed. Oc's vptr should always point to most derived object's destructor, which is not possible if vptr is static.

Undamped answered 1/7, 2014 at 3:51 Comment(0)
M
1
class A{
public:
    virtual void f1(){}
}
class B: public A{
public:
    void f1(){}
}

Now consider the above example, if we make _vptr static then its memory will be allocated only once during compile time. So, _vptr will be same for the class A and as well for class B

B b;
A *p=&b;
p->f1();

Now, consider the above case. how will the compiler know that which virtual table _vptr is pointing to?

therefore, it cannot be made static as it needs to be available for each object to call its virtual table.

Marilynnmarimba answered 1/8, 2023 at 13:17 Comment(0)
A
0

The whole point of the vptr is because you don't know exactly which class an object has at runtime. If you knew that, then the virtual function call would be unnecessary. That is, in fact, what happens when you're not using virtual functions. But with virtual functions, if I have

class Sub : Parent {};

and a value of type Parent*, I don't know at runtime if this is really an object of type Parent or one of type Sub. The vptr lets me figure that out.

Ancalin answered 17/12, 2012 at 12:28 Comment(0)
A
-1

virtual method table is per class. An object contains a pointer to the run-time type vptr.

I don't think this is a requirement in the standard bust all compiles that I've worked with do it this way.

This is true even in you example.

Amy answered 17/12, 2012 at 12:31 Comment(1)
-1: Doesn't asnwer question. Question was why, not what.Bait
P
-1

@Harsh Maurya: Reason might be , Static member variables must be defined before Main function in the program. But if we want _vptr to be static, whose responsibility ( compiler/programmer ) to define the _vptr in the program before main. And how programmer knows the pointer of VTABLE to assign it to _vptr. Thats why compiler took that responsibility to assign the value to pointer(_vptr). This happens in Constructor of class(Hidden functionality). And now if Constructor comes into picture there should be one _vptr for each object.

Precipitation answered 31/3, 2018 at 15:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.