Practical experience 79 beware of object cutting

Keywords: C++ OOP

The book Thinking in C + + discusses object slicing as follows: if a derived class object is forcibly transformed upward into a base class by passing values instead of pointers and references, the derived class object will be cut into a base class object after upward transformation, that is, the unique member variables and methods in the derived class will be cut off, Only the same member variables and properties as the base class are left. The derived class object is cut into a base class object.

Object slice reference inheritance and polymorphism correlation, but also increase its complexity. Let's look at a simple slice example without virtual inheritance.

// Human data type
class CPerson
{
public:
     char *GetName();
     void  SetName(char *pszName);
private:
     char *m_pszName;
};
//  Student class
class CStudent: public CPerson
{
public:
     char *GetSchool();
     void  SetSchool(char *pszSchool);
     
private:
     char *m_pszSchoolName;
};

CStudent Xiaoming;
Xiaoming.SeName("Lee XX");
Xiaoming.SetSchool("Tsinghua affiliated high school");

CPerson Person;
Person= Xiaoming;  // Object slicing occurs

char *pszSchool = Person.GetSchool();  // Illegal access to GetSchool() via Preson.
char *pszName = Person.GetName();

Object slicing occurs when Xiaoming is copied to Person. There is only CPerson information in the Person object, so the information of CStudent class is sliced. In "Person = Xiaoming;" The default copy constructor is also involved, which will be described in detail below.

Pay attention to distinguish the following cases. There are compilation problems due to inconsistent types, and no object slicing occurs.

CStudent Xiaoming;
CPerson *pStudent = &Xiaoming;

It is still illegal to access Xiaoming or GetSchool() through pStudent:

pStudent->GetSchool();  // Dynamic is required_ Cast < > type conversion
pStudent->GetName();

Because there is no virtual mechanism, polymorphism does not work, and no object slicing occurs. Because CStudent is a CPerson, "CPerson * pStudent = & Xiaoming" is legal. pStudent is a pointer that refers to Xiaoming, but the compiler only knows about the application object of the pointer, and knows nothing about the information of the CStudent class.

So how does object slicing happen? In short, it happens when the compiler inserts code into the copy constructor. Since the copy constructor generated by the compiler in "Person = Xiaoming" does not need additional processing on the virtual mechanism, all information belonging to CStudent is lost according to bitwise copy. In "cperson * pstudent = & Xiaoming", there is no need to call copy construction at all, so slicing will not occur.

If you add a virtual mechanism to CPerson and CStudent, see what happens? As shown in the following code snippet:

The compiler will add processing code for the virtual mechanism to your construct or the construct generated by the compiler for you, which also makes the problem of default copy constructor and object slicing extremely complex. Here, the virtual processing mechanism includes virtual functions and virtual base classes.

// Human data type
class CPerson
{
public:
     virtual char *GetName();
     void  SetName(char *pszName);
private:
     char *m_pszName;
};
//  Student class
class CStudent: public CPerson
{
public:
     char *GetSchool();
     void  SetSchool(char *pszSchool);
     virtual char *GetName() {}
     
private:
     char *m_pszSchoolName;
};

CStudent Xiaoming;
Xiaoming.SeName("Lee XX");
Xiaoming.SetSchool("Tsinghua affiliated high school");

CPerson Person;
Person = Xiaoming;  // Object slicing occurs

char *pszSchool = Person.GetSchool();  // Illegal access to GetSchool() via Preson.
char *pszName = Person.GetName();

When there is no virtual mechanism in the class and no members of other class objects, the default copy structure is shallow copy, which will lead to object slicing. However, when there is a virtual mechanism in the class or other class object members, the default copy structure adopts deep copy, and the virtual mechanism will be copied correctly.

Because of the virtual mechanism, when class objects are generated, the compiler will add the code to initialize VTable and vbaseclassable to the constructor, so as to ensure that the content in VTable matches the type exactly. In other words, both CPerson and CStudent have similar VTable, but not exactly the same. For example, CStudent can also define its own virtual function, so that its VTable will have more table entries.

The implementation of polymorphism is realized by parsing the function call into the offset in VTABLE. Pstudent - > getname() may be resolved by the compiler to:

(*Person->__vtable[Offset_of_ GetName])();

When CPerson is used as a virtual base class, the data members accessing it may be:

Person->__vBaseClassCPerson->__vtable[Offset_of_ GetName]);

Then, when the assigned copy of "Person = Xiaoming" performs a shallow copy, the VTABLE in the Person object will be Xiaoming's VTABLE, so Person.GetName() will call GetName() in CStudent, which obviously does not conform to semantics and logic.

C# and java prohibit multiple inheritance and treat the interface as a separate thing, eliminating the complexity caused by assignment copy.

Please remember

  • In the case of virtual mechanism, object slicing and copy construction are closely combined, and object slicing will become more complex.
  • C# and Java prohibit multiple inheritance and treat the interface as a separate thing, eliminating the complexity caused by assignment copy.

Posted by gamesmstr on Wed, 01 Sep 2021 19:46:11 -0700