Inheritance, Diamond Inheritance and Virtual Inheritance

Inheritance, diamond inheritance and virtual inheritance

  • 1. Concept
  • 2. Define the format
  • 3. Inheritance method
  • 4. Changes in access methods of derived classes inheriting base class members
  • 5. Assignment and conversion of base class and derived class objects
    • 1. Concept
    • 2. Schematic diagram
    • 3. Sample code
    • 4. Features
  • 6. Scope in inheritance
    • 1. Concept
    • 2. Sample code
    • 3. Operation results
  • 7. Default member functions of derived classes
    • 1. Call method
    • 2. Sample code
    • 3. Operation results
  • 8. Friendship relationships cannot be inherited.
    • 1. Code
    • 2. Pay attention
    • 3. Operation results
    • 4. Error codes and compiler errors
  • 9. Inheritance Category
    • 1. Single inheritance
      • (1) Concept
      • (2) Schematic diagram
    • 2. Multiple inheritance
      • (1) Concept
      • (2) Schematic diagram
    • 3. Diamond inheritance
      • (1) Concept
      • (2) Schematic diagram
      • (3) Disadvantages
      • (4) Sample code
      • (5) Debugging results
      • (6) Error reported by the compiler when not explicitly specified
    • 4. Virtual inheritance
      • (1) Definition and function
      • (2) Code
      • (3) Data redundancy code
      • (4) Debug memory window
      • (5) Virtual inheritance
      • (6) Debug memory window
    • 5. Summary

1. Concept

  • The inheritance mechanism is the most important means of object-oriented programming to enable code reuse. It is one of the three major features of C++ (encapsulation, inheritance, polymorphism), and it is also the reuse of class design levels.
  • Inheritance allows programmers to extend and add functionality while maintaining the characteristics of the original class. The new class generated through this method is called a derived class, also called a subclass; the original class is called a base class, also called a parent class.
  • Inheritance presents the hierarchical structure of object-oriented programming and reflects the cognitive process from simple to complex.

2. Define format

3. Inheritance method

  • Inheritance methods generally use public inheritance or protected inheritance, and private inheritance is rarely used.

4. Changes in access methods of derived classes inheriting base class members

  • No matter how a derived class inherits a base class, members of the base class that are qualified by the private qualifier will be inherited into the derived class, but the derived class cannot access them either within the class or outside the class.
  • If the members of the base class do not want to be accessed directly outside the base class, but also want to be accessible in the derived class that inherits it, the members of the base class need to be qualified with the protected qualifier.
  • The access method of members of the base class in the subclass is the member with the least privilege among the access qualifiers and inheritance methods of the base class. Access permissions from large to small are public, protected, and private.
  • Although the keywords class and struct have default inheritance modes of private and public respectively, it is best to write the inheritance mode explicitly.
  • Because the base class members inherited through protected or private inheritance can only be used in the derived class, in practice the extension and maintenance are not strong. Therefore, in actual applications, the public inheritance method is generally used, and the use of protected or private inheritance method is rarely and not advocated.

5. Assignment conversion of base class and derived class objects

1. Concept

  • Derived class objects can be assigned to base class objects, pointers, and references. This assignment method can be vividly called slicing or cutting, that is, cutting out the part of the parent class in the derived class and assigning it to or pointing to that space.
  • Base class objects cannot be assigned to derived class objects, because there may be members in the derived class that the base class does not have, and when assigning values to the derived class using the base class, those members will not be assigned or initialized.
  • A pointer or reference from a base class can be assigned to a pointer or reference from a derived class through cast. But it is safe only when the pointer of the base class points to the derived class object.

2. Schematic diagram

3. Sample code

class Person
{<!-- -->
protected:
string _name = "snowdragon";
size_t_age = 18;
};

class Student :public Person
{<!-- -->
public:
string _id;
};

int main()
{<!-- -->
Student s;
Person p = s;
Person* pp = &s;
Person & rp = s;

//s = p;

Student* ps1 = (Student*)pp;
ps1->_id = "123";
cout << ps1->_id << endl;

pp = &p;
Student* ps2 = (Student*)pp;
\t
// Below is out-of-bounds access
//ps2->_id = "123";
//cout << ps2->_id << endl;

return 0;
}

4. Features

  • Derived class objects can be assigned to base class objects as well as pointers and references to base class objects.
  • Base class objects cannot be assigned to derived class objects.
  • The pointer of the base class can be assigned to the pointer of the derived class through forced type conversion. If the base class pointer points to a derived class object, no access error will occur; if the base class pointer points to a base class object, there will be an out-of-bounds access. question. Because there may be members in the derived class that the base class does not have, and using pointers from the derived class to access these members is an out-of-bounds access, and the compiler will crash at runtime.

6. Scope in inheritance

1. Concept

  • In the inheritance system, base classes and derived classes have independent scopes.
  • When there is a member function with the same name in a derived class and a base class, the member function in the derived class will shield the base class from direct access to the member function with the same name. This situation is called hiding, also known as redefinition. But in derived class member functions, you can use base class::base class member to access explicitly.
  • If it is to hide a member function, only the function names need to be the same.

2. Sample code

class Person
{<!-- -->
public:
voidFunc()
{<!-- -->
cout << "Person::void Func()" << endl;
}
protected:
string _name = "snowdragon";
size_t_age = 18;
};

class Student :public Person
{<!-- -->
public:
void Print()
{<!-- -->
cout << "Name:" << _name << endl;
cout << "Age:" << _age << endl;
cout << "id:" << _id << endl;
}

voidFunc()
{<!-- -->
Person::Func();
cout << "Student::void Func()" << endl;
}
protected:
string _name = "snow";
string _id = "123";
};

int main()
{<!-- -->
Student s;
s.Print();
s.Func();
return 0;
}

3. Operation results

7. Default member functions of derived classes

1. Call method

  • The constructor of the derived class must call the constructor of the base class to initialize the members of the base class. That is, when the derived class object is initialized, the constructor of the base class will be called first and then the constructor of the derived class. If the base class does not have a default constructor, the base class constructor must be explicitly called during the initialization list phase of the derived class constructor.
  • The copy constructor of the derived class must call the copy constructor of the base class to complete the copy initialization of the members of the base class in the object.
  • The assignment operator overload (operator=) of the derived class must call the assignment operator overload (operator=) of the base class to complete the assignment of members of the base class in the object.
  • In order to ensure that the derived class object cleans up the derived class members first and then the base class members, the destructor of the derived class will automatically call the destructor of the base class to clean up the corresponding base class members after being called, that is, the derived class object is parsed. When constructing, the destructor of the derived class will be called first and then the destructor of the base class.

2. Sample code

class Person
{<!-- -->
public:
Person(const char* name = "snowdragon")
:_name(name)
{<!-- -->
cout << "Person()" << endl;
}
Person(const Person & p)
:_name(p._name)
{<!-- -->
cout << "Person(const Person & amp; p)" << endl;
}
Person & amp; operator=(const Person & amp; p)
{<!-- -->
cout << "Person & amp; operator=(const Person & amp; p)" << endl;
if (this != & amp;p)
_name = p._name;
return *this;
}
~Person()
{<!-- -->
cout << "~Person()" << endl;
}
protected:
string_name;
};

class Student :public Person
{<!-- -->
public:
Student(const char* name = "snow", const char* id = "123")
:Person(name)
,_id(id)
{<!-- -->
cout << "Student()" << endl;
}
Student(const Student & s)
:Person(s)
,_id(s._id) //Cannot assign value in the function body
{<!-- -->
cout << "Student(const Student & amp; s)" << endl;
}
Student & amp; operator=(const Student & amp; s)
{<!-- -->
cout << "Student & amp; operator=(const Student & amp; p)" << endl;
if (this != & amp;s)
{<!-- -->
Person::operator=(s);
_id = s._id;
}
return *this;
}
~Student()
{<!-- -->
cout << "~Student()" << endl;
}
protected:
string _id;
};

int main()
{<!-- -->
Student s1("dragon", "18");
cout << endl;

Student s2(s1);
cout << endl;

Student s3("snowdragon", "19");
cout << endl;

s1 = s3;
cout << endl;

return 0;
}

3. Operation results

8. Friend relationships cannot be inherited

1. Code

class Student;
class Person
{<!-- -->
public:
friend void Print(const Person & amp; p, const Student & amp; s);
protected:
string _name = "snowdragon";
private:
string _sex = "man";
};

class Student :public Person
{<!-- -->
public:
string _hairColor = "black";
protected:
string _id = "123";
private:
size_t_age = 18;
};
void Print(const Person & amp; p, const Student & amp; s)
{<!-- -->
cout << "Name:" << p._name << endl;
cout << "Gender:" << p._sex << endl;
cout << "Hair Color:" << s._hairColor << endl;
/*cout << "id:" << s._id << endl;
cout << "Age:" << s._age << endl;*/
}
int main()
{<!-- -->
Person p;
Student s;
Print(p, s);
return 0;
}

2. Attention

  • A base class friend function cannot access members of the subclass that are qualified with private and protected qualifiers, but because it is a friend of the base class, it can access members of the base class.
  • Because the parameters of the base class friend function in the code above require the derived class Student, and the derived class is defined after the friend function declaration statement, the derived class needs to be declared before the declaration of the friend function, that is, before the base class.

3. Operation results

4. Error codes and compiler errors

9. Inherited categories

1. Single inheritance

(1) Concept

For inheritance relationships, a derived class has only one direct base class.

(2) Schematic diagram

2. Multiple inheritance

(1) Concept

For inheritance relationships, a derived class inherits multiple (two or more) base classes at the same time.

(2) Schematic diagram

3. Diamond inheritance

(1) Concept

It is a special case of multiple inheritance. Two different derived classes inherit from the same base class, and another derived class inherits from these two derived classes.

(2) Schematic diagram

(3) Disadvantages

  • Using diamond inheritance has the disadvantages of data redundancy and ambiguity, that is, the last derived class C will inherit two members of the base class sd.
  • Explicitly specifying access to members of the base class can solve the ambiguity problem, but the data redundancy problem cannot be solved.

(4) Sample code

class Person
{<!-- -->
public:
string _p = "snowdragon";
};
class PerDer1 :public Person
{<!-- -->
protected:
string _pd1 = "snow";
};
class PerDer2 :public Person
{<!-- -->
protected:
string _pd2 = "dragon";
};
class Derive :public PerDer1, public PerDer2
{<!-- -->
protected:
string _d = "snowdragon";
};

int main()
{<!-- -->
Derived;
//d._p = "sd";
d.PerDer1::_p = "snow sd";
d.PerDer2::_p = "sd dragon";
return 0;
}

(5) Debugging results

(6) Error reported by the compiler when not explicitly specified

4. Virtual inheritance

(1) Definition and function

  • When inheriting from a derived class, adding virtual before the inheritance method is virtual inheritance, which is used to solve the ambiguity and data redundancy problems of diamond inheritance.
  • When the last derived class in the inheritance relationship is used to access a member of the original base class, all objects access the same member. That is, when any object accesses and modifies the member, the data of the member will change.

(2) Code

class Person
{<!-- -->
public:
string _p = "snowdragon";
};
class PerDer1 :virtual public Person
{<!-- -->
protected:
string _pd1 = "snow";
};
class PerDer2 :virtual public Person
{<!-- -->
protected:
string _pd2 = "dragon";
};
class Derive :public PerDer1, public PerDer2
{<!-- -->
protected:
string _d = "snowdragon";
};

int main()
{<!-- -->
Derived;
d._p = "sd";
d.PerDer1::_p = "snow sd";
d.PerDer2::_p = "sd dragon";
return 0;
}

(3) Data redundancy code

class Person
{<!-- -->
public:
int _p;
};

class PerDer1 :public Person
//class PerDer1 :virtual public Person
{<!-- -->
public:
int _pd1;
};

class PerDer2 :public Person
//class PerDer2 :virtual public Person
{<!-- -->
public:
int _pd2;
};

class Derive :public PerDer1, public PerDer2
{<!-- -->
public:
int _d;
};

int main()
{<!-- -->
Derived;
d.PerDer1::_p = 1;
d.PerDer2::_p = 2;
d._pd1 = 3;
d._pd2 = 4;
d._d = 5;

return 0;
}

(4) Debug memory window

  • The number in the box after the address is the address of object d.
  • There are two copies of the base class member _p. The first copy is inherited by class PerDer1, and the second copy is inherited by class PerDer2.

(5) Virtual inheritance

  • Below is the comment part of the data redundancy code above, that is, the comment on the class name part, and then expand the comment part.
  • In the Derive object, the Person member is placed at the bottom of the object composition, and the Person member belongs to both PerDer1 and PerDer2.
  • PerDer1 and PerDer2 point to their respective virtual base tables through their respective virtual base table pointers. There is an offset in the virtual base table. Through this offset, the following Person members can be found from the current position.

(6) Debug memory window

5. Summary

  • Because C++ has multiple inheritance, there is diamond inheritance, and with diamond inheritance there will be diamond virtual inheritance. Using them complicates the underlying implementation. Therefore, it is generally not recommended to design multiple inheritance, otherwise there will be problems in complexity and performance.
  • Public inheritance is an is-a relationship. That is, each derived class object is a base class object or an object extended by the base class. When members qualified by protected and private qualifiers are changed in the base class, it may affect the derived class, that is, the coupling degree is high.
  • Combination is a has-a relationship. If B composes A, there will be an A object within every B object. To put it simply, an object of class A is defined in class B. When changing members qualified by protected and private qualifiers in the base class, it may not affect the derived class, that is, the degree of coupling is low.
  • Inheritance allows the implementation of a derived class to be defined based on the implementation of the base class. This kind of reuse by generating derived classes is often called white-box reuse. Although in the inheritance method, the internal details of the base class are visible to the subclass, it also destroys the encapsulation of the base class to a certain extent. Therefore, changes in the base class have a great impact on the derived class. The dependency between derived classes and base classes is very strong and the degree of coupling is high.
  • Object composition is another reuse option besides class inheritance. New and more complex functions can be obtained by assembling or combining objects. Object composition requires that the objects being composed have well-defined interfaces. This style of reuse is called black-box reuse because the internal details of the object are not visible. Objects only appear as “black boxes”. There is no strong dependency between the combined classes, and the degree of coupling is low. Preferring object composition helps keep each class encapsulated.
  • Because we must follow the design concept of high cohesion and low coupling, when the relationship between classes can be combined, use combination; when combination cannot be used, consider using inheritance. Because the coupling of the combination is low, the code maintainability is good.

This article ends here. If there are any errors or unclear points, please comment or send a private message.
Creation is not easy. If you think the blogger’s writing is good, please be sure to like, collect and follow