1

In object slicing, when a derived class object is copied to a Base class object, does the _vptr of Derived class also get copied to _vptr of Base class, like other members of class Base? If not, why?

class Base {
public :
    virtual void Display() { cout << "In Base" << endl; }
};
class Derived:public Base {
public:
    void Display() { cout << "In Derived" << endl; }
};
int main()
{
    Derived objD;
    Base objB;
    objB = objD;
    objB.Display();
}

I have observed the following result for the above snippet.

Output
In Base
U. Windl
  • 3,480
  • 26
  • 54

2 Answers2

5

The vptr is NOT copied. Let's try to reason about why.

Looking at your main function:

Derived objD;
Base objB;
objB = objD;  // <-- HERE
objB.Display();

In line 3, you are assigning objD to objB. This is actually calling Base's assignment operator (which is automatically defined):

Base& operator=(const Base& other)

and it is being passed objD as a Base&. So, your question becomes, "Does the assignment operator copy the vptr"? The answer is, "no". The default assignment operator only copies fields on the class.

You may then ask, "Why wouldn't it copy the vptr too?" The reason is that, if it copied the vptr, methods on the Base class would end up using methods implemented on the Derived class. However, in full generality, those methods could use data members that only exists on the Derived class (and that don't exist on the Base class). Calling those methods would therefore be nonsensical (the data logically doesn't exist on the Base class), and so the language rightly chooses not to do this.

The main issue is that, when you assign the Derived class to the Base class, the variable you're assigning to only holds the fields for the Base class, so the fields in the Derived class that aren't in the Base class are not copied. Therefore, methods from the Derived class won't make sense when called on the Base class.

Note that this isn't the case if, instead, you were to assign a Base pointer or a Base reference to the Derived class. In that case, the original Derived class instance still exists. It's sitting in memory somewhere and has all the Base+Derived class fields. Therefore, methods for the Derived class called on that instance will have access to those fields, and so being able to call those methods still makes sense.

This is why, in C++, to do polymorphism (via virtual methods), you need to use a reference or pointer. See here for a similar discussion: Why doesn't polymorphism work without pointers/references?

GeorgeLewis
  • 255
  • 4
  • 8
  • Wouldn't the short answer be like this?: "As dynamic binding can only work for pointers/references, and vptr/vtbl is used to implement dynamic binding, it makes no sense to copy that (as it won't be used anyway)" – U. Windl Jun 02 '23 at 10:07
-1

All known implementations use vptr but the details vary a lot. The vptr is a way for implementations to represent the type of an object of a polymorphic class (class with virtual functions) and sometimes classes with virtual base classes (very implementation dependent).

Note that in many cases involving more than simple inheritance (single non virtual inheritance) classes have more than one vptr.

The vptr value is a function of the type of the most derived object constructed.

The type of an object cannot be changed by the user during its lifetime; during construction, the type of the object changes as it is being constructed; during destruction, it changes back. (It is illegal to refer to an object during construction or destruction with a name or other expression that has a type that doesn't match the object.)

You can reuse the space of an existing object to construct an object of a different type, but it isn't the same object:

struct X {
  A a;
  B b;
  // requires: construction doesn't throw
};

void replace (X &x) {
  x.~X(); 
  B &b = *new (&x) B; // hope no exception here
  b.f(); // use as a normal B object
  x.f(); // undefined: cannot use x as a real object
  X *p = &x; // legal: x isn't used as an object, only as an address
  b.~B(); // clean up ressources if needed
  new (&x) X; // valid: &x refers to storage, as if a void*
  // x refers to a valid X object 
}

Such object reuse doesn't change the type of any existing object: replace(x) doesn't just act on x (it does by destructing one and reconstructing one), it acts on the storage location of x. After calling replace(x) the name x can be used to refer to the newly constructed X object, which has effectively taken the identify of the previously existing X object.

No operation on an object (that keeps the object alive) is even allowed to change its type in C++. There is no way for a vptr to change on a constructed object.

Changing the type of a declared object would wreck the C++ types system and the invariants of declared objects. The compiler wouldn't know what class to destroy, which virtual functions to call, where the base classes are, etc. It would completely change the way C++ works. It's hard to even imagine the semantics associated with changing the type of an existing object.

curiousguy
  • 8,038
  • 2
  • 40
  • 58