c/c++ development, inevitable custom class type (Part 3). Class and virtual function

In-depth and comprehensive understanding of the relationship between virtual functions and class design

Table of contents

1. The role of the virtual function specifier

Two, virtual member function and ordinary member function

3. It is not necessary to redefine the virtual function for each derived class

4. Derived classes can take into account the virtual function capabilities of the base class

5. Override virtual function mechanism

        5.1 virtual coverage mechanism

        5.2 Virtual function coverage is related to inheritance mode, derivation behavior, etc.

        5.3 override specifier

        5.4 final specifier

6. Virtual functions and default arguments

Seven, virtual destructor

        7.1 Ensure that the entire inheritance system effectively releases resources

        7.2 It is good practice to keep virtual destructors for root classes

8. Constructors and assignment operators cannot declare virtual functions

        8.1 Forbid constructors to be virtual functions

        8.2 It does not make sense to declare an assignment operator as a virtual function

9. Virtual functions in constructors and destructors

       9.1 Handle virtual functions carefully in order of construction and destruction

        9.2 Main type integrity of calling a virtual function within a constructor or destructor

10. Virtual function masking

11. Pure virtual functions

12. Exception description and virtual function

Thirteen, multiple inheritance of virtual functions

        13.1 Virtual inheritance avoids base class ambiguity

        13.2 In case of multiple inheritance, ensure the uniqueness of virtual function overload resolution candidates

14. Runtime type identification (RTTI) and virtual functions

        14.1 Run-Time Type Identification (RTTI)

        14.2 dynamic_cast operator and virtual functions

        14.3 The typeid operator and virtual functions


1. The role of the virtual function specifier

        The virtual specifier designates a non-static member function as virtual and supports dynamic call dispatch. It can appear only in the declaration-specifier sequence of the first declaration of a non-static member function (that is, when it is declared in a class definition). In C++, the base class must indicate which functions it wants the derived class to rewrite. The functions defined as virtual are those that the base class expects the derived class to redefine. The functions that the base class expects the derived class to inherit cannot be defined as virtual functions .

#include <iostream>
class Base {
    public:
    virtual void f() {
        std::cout << "Base\n";
    }
};

class Derived : public Base {
    public:
    void f() override { // 'override' 可选
        std::cout << "Derived\n";
    }
};

void virtual_first_test(void)
{
    Base b;
    Derived d;
 
    // 通过引用调用虚函数
    Base& br = b; // br 的类型是 Base&
    Base& dr = d; // dr 的类型也是 Base&
    br.f(); // 打印 "Base"
    dr.f(); // 打印 "Derived"
 
    // 通过指针调用虚函数
    Base* bp = &b; // bp 的类型是 Base*
    Base* dp = &d; // dp 的类型也是 Base*
    bp->f(); // 打印 "Base"
    dp->f(); // 打印 "Derived"
 
    // 非虚函数调用,指定类成员函数版本
    br.Base::f(); // 打印 "Base"
    dr.Base::f(); // 打印 "Base"
};

         In C++, dynamic binding occurs when a virtual function is called through a reference (or pointer) to a base class. The fact that a reference (or pointer) can point to both a base class object and a derived class object is the key to dynamic binding. A virtual function called by a reference (or pointer) is determined at runtime, and the called function is defined by the actual type of the object pointed to by the reference (or pointer). When dealing with derived classes using pointers or references to the base class, calls to the overridden virtual functions will invoke the behavior defined in the derived class. Such a function call is called a virtual function call or virtual call. Virtual function calls are suppressed when using qualified name lookup (ie the function name appears on the right side of the scope resolution operator ::).

        A virtual function is a member function whose behavior can be overridden in a derived class. If a virtual function is called by a reference or a pointer, the compiler will generate code, and it is not possible to determine which function to call until runtime. The virtual function that is run is the version defined by the type that the reference is bound to or the object pointed to by the pointer.

        Non-member functions and static member functions cannot be virtual . Function templates cannot be declared asvirtual , this only applies to functions that are themselves templates. Regular member functions of class templates can be declared virtual.

Two, virtual member function and ordinary member function

        For ordinary member functions of the base class, when the member function of the derived class declares the same (same name and parameter list), the member function of the derived class will be overwritten by the member function of the base class and cannot take effect, that is, if a non-virtual function is called, no matter Whatever type the actual object is, execute the function defined by the base class type.

#include <iostream>
class Base {
    public:
    virtual void f() {
        std::cout << "Base\n";
    }
    void g(){
        std::cout << "Base g()\n";
    };
};

class Derived : public Base {
    public:
    void f() override { // 'override' 可选
        std::cout << "Derived\n";
    }
    void g(){   //被覆盖,无效
        std::cout << "Base g()\n";
    };
};

void virtual_first_test(void)
{
    Base b;
    Derived d;
    b.f();  // 打印 "Base"
    d.f();  // 打印 "Derived"
    b.g();  //打印 "Base g()"
    d.g();  //打印 "Base g()"
    d.Derived::g();//打印 "Base g()"
    // 通过引用调用虚函数
    Base& br = b; // br 的类型是 Base&
    Base& dr = d; // dr 的类型也是 Base&
    br.f(); // 打印 "Base"
    dr.f(); // 打印 "Derived"
    br.g(); // 打印 "Base g()"
    dr.g(); // 打印 "Base g()"
 
    // 通过指针调用虚函数
    Base* bp = &b; // bp 的类型是 Base*
    Base* dp = &d; // dp 的类型也是 Base*
    bp->f(); // 打印 "Base"
    dp->f(); // 打印 "Derived"
    bp->g(); // 打印 "Base g()"
    dp->g(); // 打印 "Base g()"
 
    // 非虚函数调用
    br.Base::f(); // 打印 "Base"
    dr.Base::f(); // 打印 "Base"
};

        The above code shows that function calls in C++ do not use dynamic binding by default.

        To trigger dynamic binding, two conditions are met:

        First, only member functions designated as virtual functions can be dynamically bound, member functions are non-virtual functions by default, and non-virtual functions are not dynamically bound;

        Second, the function call must be made through a reference or pointer of the base class type.

3. It is not necessary to redefine the virtual function for each derived class

        Base classes should generally define as virtual any functions that derived classes need to redefine. Although not required to do so, derived classes typically redefine inherited virtual functions. If the derived class does not redefine a virtual function, the version defined in the base class is used.

#include <iostream>
class Base {
    public:
    virtual void f() {
        std::cout << "Base\n";
    }
};

class Derived_1 : public Base {
    public:
};

void virtual_first_test(void)
{
    Base bobj;
    Derived_1 dobj;
    bobj.f();   // 打印 "Base"
    dobj.f();   // 打印 "Base"
    Base* bpobj = &bobj; // bp 的类型是 Base*
    Base* dpobj = &dobj; // dp 的类型也是 Base*
    bpobj->f(); // 打印 "Base"
    dpobj->f(); // 打印 "Base"
};

        If it is necessary to implement a function different from that of the base class virtual function in the derived class, the derived type must declare each inherited member that needs to be redefined. 

4. Derived classes can take into account the virtual function capabilities of the base class

        In the implementation of the virtual function in the derived class, in addition to implementing its own behavior, it can also explicitly call the base class function.

#include <iostream>
class Base {
    public:
    virtual void f() {
        std::cout << "Base\n";
    }
};

class Derived_2 : public Base {
    public:
    void f()
    {
        Base::f();
        std::cout << "Derived_2::f\n";
    }
};

void virtual_first_test(void)
{
    Derived_2 dobj2;
    dobj2.f();              // 打印 "Base和Derived_2::f"
    Base* dpobj2 = &dobj2;  // dp 的类型也是 Base*
    dpobj2->f();            // 打印 "Base和Derived_2::f"
};

        Once a function is declared virtual in the base class, it remains virtual and derived classes cannot change the fact that the function is virtual. When a derived class redefines a virtual function, the virtual reserved word can be used, but it is not required to do so.

5. Override virtual function mechanism

        The above code embodies the overriding virtual function mechanism.

        5.1 virtual coverage mechanism

        In some cases it is desirable to override the virtual function mechanism and force a function call to use a specific version of the virtual function, the scope operator can be used.

#include <iostream>
class Base {
    public:
    virtual void f() {
        std::cout << "Base\n";
    }
};

class Derived : public Base {
    public:
    void f() override { // 'override' 可选
        std::cout << "Derived\n";
    }
};
class Derived_2 : public Base {
    public:
    void f()
    {
        Base::f();    //覆盖机制
        std::cout << "Derived_2::f\n";
    }
};
void virtual_first_test(void)
{
    Base b;
    Derived d;
    // 通过引用调用虚函数
    Base& br = b; // br 的类型是 Base&
    Base& dr = d; // dr 的类型也是 Base&
    // 非虚函数调用
    br.Base::f(); // 打印 "Base" ,覆盖机制
    dr.Base::f(); // 打印 "Base" ,覆盖机制
    Derived* dp1 = &d;
    dp1->Derived::f();// 打印 "Derived",覆盖机制
};

        The most common use of the override mechanism is to call the base class version of a derived class virtual function. In this case, the base class version can do the common tasks of all types in the inheritance hierarchy, while each derived type just adds its own special work. PS: When a derived class virtual function calls the base class version, the scope operator must be explicitly used. If the derived class function omits to do so, the function call is determined at runtime and will be a call to itself, resulting in infinite recursion.

        5.2 Virtual function coverage is related to inheritance mode, derivation behavior, etc.

        Every virtual function has a final override function, which is the function that is executed when the virtual function call is made. A virtual member function f of the base class Base is the ultimate overriding function, unless a derived class declares or (through multiple inheritance) inherits another function that overrides f.

#include <iostream>
class Base {
    public:
    virtual void f() {
        std::cout << "Base\n";
    }
};

class Derived : public Base {
    public:
    void f() override { // 'override' 可选,覆盖,Base::f()
        std::cout << "Derived\n";
    }
};

class DDerived1 : virtual public Derived {  //虚继承
    public:
    void f() override { // 'override' 可选,覆盖Base::f()
        std::cout << "DDerived2\n";
    }
};
class DDerived2 : public Derived {
    public:
    void f() override { // 'override' 可选,覆盖Derived::f()
        std::cout << "DDerived2\n";
    }
};
class DDerived3 : virtual public Derived {//虚继承,不引入覆盖函数,最终覆盖函数是Derived::f()
    public:
};
class DDDerived : public DDerived1,DDerived3 {};//不引入覆盖函数,最终覆盖函数是 DDerived1::f

        Another thing to note about the overriding mechanism of virtual functions is:

  •         If a function has more than one final overriding function, the program is ill-formed.
  •         If a function is declared with the specifier override, but does not override any virtual functions, the program is ill-formed.
  •         If a function declared with the specifier final is attempted to be overwritten by another function, the program is ill-formed.

        5.3 override specifier

        The override specifier (since C++11), specifies that a virtual function overrides another virtual function, and its syntax expresses:

/*如果使用标识符 override,那么它紧随成员函数声明或类定义内的成员函数定义语法中的声明符之后出现。*/
 
/*格式【1】:*/声明符 虚说明符序列(可选) 纯说明符(可选) //在成员函数声明中,override 可以在紧随声明符之后并在 纯说明符 之前(如有使用)的 虚说明符序列 中出现。 
 
/*格式【2】:*/声明符 虚说明符序列(可选) 函数体 //在类定义内成员函数定义中,override 可以在紧随声明符之后并紧接 函数体 之前的 虚说明符序列 中出现。

        In the declaration or definition of a member function, the override specifier ensures that the function is virtual and overrides a virtual function in a base class. If not, the program is ill-formed (generates compilation errors).

       Note: override is an identifier that has special meaning when used after a member function declarator, otherwise it is not a reserved keyword.

class A_override
{
public:
    virtual void foo();
    void bar();
};

class B_override : public A_override
{
public:
    // void foo() const override; // 错误:B::foo 不覆盖 A::foo,签名不匹配
    void foo() override; // OK:B::foo 覆盖 A::foo
    // void bar() override; // 错误:A::bar 非虚
};

        5.4 final specifier

        final specifier (since C++11), a virtual function cannot be overridden in a derived class, or a class cannot be derived, its grammatical expression:

/*当应用到成员函数时,标识符final在类定义中的成员函数声明或成员函数定义的语法中
*,紧随声明符之后出现。当应用到类时,标识符final在类定义的开头,紧跟类名之后出现。*/
 
/*格式【1】:
*在成员函数声明中,final可以在紧跟声明符之后的虚说明符序列中出现,如果有使用纯说明符则在其之前。*/
声明符 虚说明符序列(可选) 纯说明符(可选) //

/*格式【2】:
*在类定义内的成员函数定义中,final可以在紧跟声明符之后并紧接函数体之前的虚说明符序列中出现。*/
声明符 虚说明符序列(可选) 函数体 //
 
/*格式【3】:
*在类定义中,final可以在紧跟类名之后,紧接基类子句(若使用它)起头的冒号之前,
*作为类虚说明符 出现。*/
类关键词 attr(可选) 类头名 类虚说明符(可选) 基类子句(可选) // 

/*格式(1,2) 中,如果有使用虚说明符序列,那么它是 override、final、final override 
*或 override final 之一。情况 (3) 中,如果有使用类虚说明符则只允许final。*/

        Notes on using the final specifier:

        When used in a virtual function declaration or definition, the final specifier ensures that the function is virtual and specifies that it cannot be overridden by derived classes, otherwise the program is ill-formed (generates a compile-time error);

        When used in a class definition, final specifies that this class must not appear in the base class specifier list in another class's definition (in other words, cannot be derived from it), otherwise the program is ill-formed (generates a compile-time error);

        final can also be used in union definitions, in which case it has no effect (except for the output of std::is_final) (since C++14), because unions cannot be derived from;

        final is an identifier that has a special meaning when used in a member function declaration or in a class header. It is not reserved in other contexts and can be used to name objects or functions.

class A_final
{
public:
    virtual void foo();
};
 
class B_final : A_final
{
public:
    void foo() final; // A_final::foo 被覆盖而 B_final::foo 是最终覆盖函数
    // void bar() final; // 错误: bar 非虚,因此它不能是 final 的
};
 
class C_final final : B_final // class C_final 为 final
{
public:
    // void foo() override; // 错误:foo 不能被覆盖,因为它在 B_final 中是 final 的
};
 
// class D_final : C_final // 错误:C_final 是 final 的
// {
//     public:
// };

6. Virtual functions and default arguments

        Like any other function, a virtual function can have default arguments. Normally, if there is a default argument value for a given call, that value will be determined at compile time. If a call omits an argument with a default value, the value used is defined by the type of the calling function, independent of the dynamic type of the object. When a virtual function is called through a reference or pointer of a base class, the default argument is the value specified in the declaration of the base class virtual function. If the virtual function is called through a pointer or reference of a derived class, the default argument is the value specified in the version of the derived class. declared value.

#include <iostream>
class Base {
    public:
    virtual void func(int val_=1) {//默认实参=1
        std::cout << "Base::func("<< val_<<")\n";
    }
};

class Derived : public Base {
    public:
    void func(int val_=10) {    //默认实参=10,但通过基类指针调用时,采用基类的
        std::cout << "Derived::func("<< val_<<")\n";
    }
};

class Derived_1 : public Base {
    public:
    void func(int val_) {    //虽然不指定默认实参,但实际与基类一致
        std::cout << "Derived_1::func("<< val_<<")\n";
    }
};
void virtual_first_test(void)
{
    Base barg;
    Derived darg;
    Derived_1 darg1;
    Base& pbarg = barg;
    Base& pdarg = darg;
    Base& pdarg1 = darg1;
    pbarg.func();   //Base::func(1)
    pdarg.func();   //Derived::func(1)
    pdarg1.func();  //Derived_1::func(1)
    darg.func();    //Derived::func(10)
};

        Using different default arguments in the base and derived versions of the same virtual function is almost guaranteed to cause trouble. Problems can arise when a virtual function is invoked through a base class reference or pointer, but the version defined in the derived class is actually executed. In this case, the default arguments defined for the base-class version of the virtual function are passed to the derived-class version defined with different default arguments.

Seven, virtual destructor

        7.1 Ensure that the entire inheritance system effectively releases resources

        If you delete the base class pointer, you need to run the base class destructor and clean up the members of the base class, if the object is actually of a derived type, the behavior is not defined. To ensure that the proper destructor is run, the destructor in the base class must be virtual.

class Base1
{
private:
    /* data */
    char *pstr;
public:
    Base1(int size=10);
    virtual ~Base1();    //虚析构函数
    void f();
};

Base1::Base1(int size)
{
    pstr = (char*)malloc(size*sizeof(char));
    std::cout << "Base1\n";
}

Base1::~Base1()
{
    delete[] pstr;
    pstr = nullptr;
    std::cout << "~Base1\n";
}

void Base1::f()
{
    char str[] = "hello";
    memcpy(pstr,str,sizeof(str));
    std::cout << "Base1::f\n";
};

class Derived_3 : public Base1 {
    public:
    Derived_3(){std::cout << "Derived_3\n";};
    ~Derived_3(){std::cout << "~Derived_3\n";};
    //virtual ~Derived_3(){std::cout << "~Derived_3\n";};//本质是虚函数声明定义
    //或采用默认构造、析构
    void func(){
        std::cout << "Derived_3::func\n";
        f();
    }
};
class Derived_4 : public Derived_3 { };

void destructorTest(void)
{
    try{
        Derived_3 *pd3 = new Derived_3();
        pd3->func();
        delete pd3;
        std::cout << "------------------\n";
        Derived_4 d4;
        d4.func();
        std::cout << "------------------\n";
        Base1& b1 = d4;
        b1.f();
    }catch(...)
    {
        std::cout << "Derived_3::mem\n";
    }
}
//out log
Base1
Derived_3
Derived_3::func
Base1::f
~Derived_3
~Base1
------------------
Base1
Derived_3
Derived_3::func
Base1::f
------------------
Base1::f
~Derived_3
~Base1

        The virtual destructor has an important influence on the design of the base class to automatically call the destructor of the base class part. When you delete a pointer to a dynamically allocated object, you need to run the destructor to clean up the object before freeing the object's memory. When dealing with objects in an inheritance hierarchy, the static type of the pointer may differ from the dynamic type of the object being deleted, possibly deleting a base class type pointer that actually pointed to a derived class object.

        7.2 It is good practice to keep virtual destructors for root classes

        If the destructor is virtual, then when called through a pointer, which destructor is run will vary depending on the type of object pointed to by the pointer. Whether it is a base class or a derived class, if the member variable has dynamically allocated memory, it is best to declare the destructor as virtual . Like other virtual functions, the virtual nature of the destructor will be inherited. Therefore, if the destructor of the root class in the hierarchy is virtual, the derived class destructor will also be virtual , whether the derived class explicitly defines a destructor or uses a synthetic destructor, the derived class destructor is virtual function.

        If a class needs a destructor, the class almost does need other copy control members as well. Base classes almost always require a constructor, making it possible to make a destructor virtual.

        If the base class has an empty destructor in order to make the destructor virtual, then having a class with a destructor does not mean that an assignment operator or copy constructor is also required. Even if the destructor has no work to do, the root class of the inheritance hierarchy should define a virtual destructor.

class Base2
{
public:
    virtual ~Base2(){}; //作为根类,最好给予virtual声明定义
};

class Derived_5 : public Base2 
{
    private:
    char* pstr;
    public:
    Derived_5(){pstr = (char*)malloc(10*sizeof(char));}
    ~Derived_5(){delete[] pstr; pstr=nullptr;}  //实际为virtual函数
};

8. Constructors and assignment operators cannot declare virtual functions

        Among the copy control members, only the destructor should be defined as a virtual function, and the constructor cannot be defined as a virtual function.

        8.1 Forbid constructors to be virtual functions

        The constructor is run before the object is fully constructed, and at the time the constructor runs, the object's dynamic type is not complete. Although it is possible to define the member function operator= as virtual in a base class, doing so does not affect the assignment operator used in derived classes. Each class has its own assignment operator, and an assignment operator in a derived class has a parameter of the same type as the class itself, which must be different from the parameter type of the assignment operator of any other class in the inheritance hierarchy.

class Base3
{
private:
    char* pstr;
public:
    Base3(){pstr = (char*)malloc(10*sizeof(char));};
    // virtual Base3(){pstr = (char*)malloc(10*sizeof(char));};//error,constructors cannot be declared 'virtual'
    virtual ~Base3(){delete[] pstr; pstr=nullptr;}; //作为根类,最好给予virtual声明定义
};

class Derived_6 : public Base3 {};

        8.2 It does not make sense to declare an assignment operator as a virtual function

        Making the assignment operator= virtual can be confusing and not useful because virtual functions must have the same parameters in both the base and derived classes. A base class assignment operator has a parameter that is a reference to its own class type. If the operator is a virtual function, each class will get a virtual function member that defines operator= whose parameter is a base class object. However, for derived classes, this operator is different from the assignment operator.

class Base3
{
private:
    char* pstr;
public:
    Base3(){pstr = (char*)malloc(10*sizeof(char));};
    // virtual Base3(){pstr = (char*)malloc(10*sizeof(char));};//error,constructors cannot be declared 'virtual'
    virtual ~Base3(){delete[] pstr; pstr=nullptr;}; //作为根类,最好给予virtual声明定义
    // virtual Base3& operator=(const Base3& obj);  //可定义,但无意义
    Base3& operator=(const Base3& obj){
        if (this== &obj)
        {
            return *this;
        }
        delete[] pstr;
        int size = sizeof(obj.pstr);
        pstr = (char*)malloc(size);
        memcpy(pstr,obj.pstr,size);
        return *this;
    };
};

class Derived_6 : public Base3 {};

9. Virtual functions in constructors and destructors

       9.1 Handle virtual functions carefully in order of construction and destruction

         When constructing a derived class object, first run the base class constructor to initialize the base class part of the object. The derived class portion of the object is uninitialized when the base class constructor is executed. In fact, the object is not yet a derived class object at this point. When a derived class object is revoked, its derived class part is revoked first, and then its base class part is revoked in the reverse order of construction.

class Base1
{
private:
    /* data */
public:
    Base1(){std::cout << "Base1\n";};
    virtual ~Base1(){std::cout << "~Base1\n";};
};

class Derived_3 : public Base1 {
    public:
    Derived_3(){std::cout << "Derived_3\n";};
    ~Derived_3(){std::cout << "~Derived_3\n";};
};

class Derived_4 : public Derived_3 {
    public:
    Derived_4(){std::cout << "Derived_4\n";};
    ~Derived_4(){std::cout << "~Derived_4\n";};  
};

void destructorTest(void)
{
    try{
        Derived_4 *pd4 = new Derived_4();
        delete pd4;
    }catch(...)
    {
        std::cout << "Derived_3::mem\n";
    }
}
//out log
Base1
Derived_3
Derived_4
~Derived_4
~Derived_3
~Base1

        9.2 Main type integrity of calling a virtual function within a constructor or destructor

        In both cases, the object is incomplete when the constructor or destructor is run. To accommodate this incompleteness, the compiler treats the object's type as if it changed during construction or destruction. In a base class constructor or destructor, a derived class object is treated as if it were an object of the base class type. The object type during construction or destruction has an effect on the binding of virtual functions. If a virtual function is called in a constructor or destructor, the version defined for the constructor or destructor's own type is run . This binding applies whether a virtual function is called directly by a constructor (or destructor) or indirectly from a function called by the constructor (or destructor).

class Base1
{
private:
    /* data */
    char *pstr;
public:
    Base1(int size=10);
    virtual ~Base1();
    void f();
};

Base1::Base1(int size)
{
    //f();    //error,不完整类,程序运行异常、崩溃
    pstr = (char*)malloc(size*sizeof(char));
    std::cout << "Base1\n";
}

Base1::~Base1()
{
    f();    //OK,对象是完整的
    delete[] pstr;
    pstr = nullptr;
    std::cout << "~Base1\n";
}

void Base1::f()
{
    char str[] = "hello";
    memcpy(pstr,str,sizeof(str));
    std::cout << "Base1::f\n";
};

class Derived_3 : public Base1 {
    public:
    Derived_3(){
        f();
        std::cout << "Derived_3\n";
    };
    ~Derived_3(){
        f();
        std::cout << "~Derived_3\n";
    };
    void f(){std::cout << "Derived_3::f\n";};
    //或采用默认构造、析构
    void func(){
        std::cout << "Derived_3::func\n";
        f();
    }
};

class Derived_4 : public Derived_3 {
    public:
    Derived_4(){
        f();
        std::cout << "Derived_4\n";
    };
    ~Derived_4(){
        f();
        std::cout << "~Derived_4\n";
    };  
    void f(){std::cout << "Derived_4::f\n";};
};
void destructorTest(void)
{
    try{
        Derived_4 *pd4 = new Derived_4();
        delete pd4;
    }catch(...)
    {
        std::cout << "Derived_3::mem\n";
    }
}
//out log
Base1
Derived_3::f
Derived_3
Derived_4::f
Derived_4
Derived_4::f
~Derived_4
Derived_3::f
~Derived_3
~Base1

        To understand this behavior, consider what would happen if a derived class version of a virtual function was called from a base class constructor (or destructor). The derived class version of the virtual function will most likely access members of the derived class object, and if the derived class version does not need to use the derived class object's members, the derived class will most likely be able to use the definition in the base class. However, the members of the derived part of the object will not be initialized during the run of the base class constructor, and indeed, if such access is allowed, the program will likely crash.

10. Virtual function masking

        Virtual functions must have the same prototype in both base and derived classes. If the base class member accepts different arguments than the derived class member, there is no way to call the derived class function through a reference or pointer of the base class type.

  • A function with the same name but a different parameter list does not override a base class function with the same name, but hides it: when unqualified name lookup checks the derived class scope, the lookup finds the declaration and does not check the base class again.
  • If the name is the same and the formal parameter list is different, but the return type is inconsistent, it will cause a compilation error warning.
class Base4
{
private:
    /* data */
public:
    Base4(/* args */){};
    virtual ~Base4(){};
    virtual void f(void){std::cout << "Base4\n";};
};

class Derived_7 : public Base4 {
    public:
    void f(int val=0){std::cout << "Derived_7("<<val<<")\n";};//实参不同,屏蔽了基类虚函数实现
};
class Derived_7l : public Base4 {
    public:
    // int f(void){std::cout << "Derived_7l\n"; return 1;};//error,参数及函数名一致,返回类型不同,与基类函数引起冲突
};

class Derived_8 : public Derived_7 {
    public:
    void f(void){std::cout << "Derived_8\n";};//重新对基类虚函数声明进行虚实现
};

void virtual_shield_test(void)
{
    Base4 b4;
    Derived_7 d7;
    Derived_8 d8;
    b4.f(); // 打印 "Base4"
    d7.f(); // 打印 "Derived_7(0)"
    d7.Base4::f();// 打印 "Base4"
    d8.f(); // 打印 "Derived_8"
    Base4& rb4 = b4;
    Base4& rd7 = d7;
    Base4& rd8 = d8;
    rb4.f();    // 打印 "Base4"
    rd7.f();    // 打印 "Base4",通过Base4引用派生类,调用虚函数并不能匹配到派生类的虚实现,又回退调用基类虚实现
    rd8.f();    // 打印 "Derived_8"
};

        The f version in Derived_7 does not redefine the virtual function f of Base4, on the contrary, it shields the f of the base class. It turns out that Derived_7 has two functions named f: the class inherits a virtual function named f from Base, and the class defines its own non-virtual member function named f, which accepts an int parameter. However, the virtual function inherited from Base4 cannot be called through the Derived_7 object (or the reference or pointer of Derived_7), because the function is blocked by the definition of f(int). Therefore, when calling a function through a reference or pointer of base class type Base4, the compiler will look for the function in the base class and ignore the implementation of the derived class Derived_7.

        Class Derived_8 redefines two functions it inherits, it redefines the original version of f defined in Base4 and redefines the non-virtual version defined in Derived_7.

11. Pure virtual functions

        Write = 0 after the virtual function parameter list to designate a pure virtual function. Defining a function as pure virtual says that the function provides an overridable interface for descendant types, but the version in this class is never called. Importantly, users will not be able to create objects of type Base5.

class Base5
{
private:
    /* data */
public:
    Base5(/* args */){};
    virtual ~Base5(){};
    virtual void f(void) = 0 ;
};

class Derived_9 : public Base5 {
    public:
    void f(void){std::cout << "Derived_9\n";};
};
class Derived_10 : public Base5 {
    public:
};

void pure_virtual_test(void)
{
    // Base5 b5;//error
    Derived_9 d9;
    d9.f();
    Base5& rd9 = d9;
    rd9.f();    
    // Derived_10 d10; //error,纯虚函数必须在派生类虚实现
};

        A class that contains (or inherits) one or more pure virtual functions is an abstract base class, and the class does not have to (but can) define pure virtual functions. After a class defines a pure virtual function, no object of the abstract type can be created except as part of an object of a derived class of the abstract base class. A derived class is also abstract if it does not define its own version of an inherited pure virtual function . We define these operations that must be reimplemented by derived classes as pure virtual functions, and each derived class must define its own version of these functions.

12. Exception description and virtual function

        The exception description of the virtual function in the base class can be different from the exception description of the corresponding virtual function in the derived class. However, the exception specification for a derived class virtual function must be as strict as, or more restrictive than, the exception specification for the corresponding base class virtual function.
        This restriction ensures that when a derived class virtual function is called with a pointer to a base class type, the derived class exception specification does not add a new throwable exception.

class Base6 {
public:
    virtual double f1(double) noexcept{return 1.0;};
    virtual int f2(int) noexcept(false){ return 1;};
    virtual std::string f3() noexcept(false){ return "";};
};

class Derived_11 : public Base6 {
public:
    // error,looser exception specification on overriding virtual function 'virtual double Derived_11::f1(double) noexcept (false)
    // double f1(double) noexcept(false){ return 1.0;};
    // ok: 与基类保持一致
    int f2(int) noexcept(false){ return 1;};
    // ok: 比基类更严格
    std::string f3() noexcept{ return "";};
};

        The declaration of f1 in the derived class is wrong because its exception specification adds an exception to those listed for the base class version of f1. Derived classes cannot add exceptions to the exception specification list because users of the inheritance hierarchy should be able to write code that depends on the specification list. If function calls are made through base class pointers or references, users of these classes should only be concerned with exceptions specified in the base class.
The exceptions thrown by the derived class are limited to those listed by the base class, so you know which exceptions you must handle when you write your code .

        That is, code can rely on the fact that the list of exceptions in the base class is a superset of the list of exceptions that the derived class version of the virtual function can throw .

        The noexcept specifier (since C++11), specifies whether the function throws exceptions:

语法
noexcept         //与 noexcept(true) 相同 
noexcept(表达式) //如果 表达式 求值为 true,那么声明函数不会抛出任何异常。表达式 - 按语境转换为 bool 类型的常量表达式 
throw()         //与 noexcept(true) 相同(C++17 前的语义见动态异常说明). (C++17 起)(C++17 中弃用)(C++20 中移除) 

Thirteen, multiple inheritance of virtual functions

        13.1 Virtual inheritance avoids base class ambiguity

        A class can be a derived class that directly inherits multiple base classes. When multiple base classes inherit, you need to pay attention to the problem of member name conflicts. In applications, member name conflicts will cause ambiguity, and the compiler cannot resolve and fail to compile. At this time, you need to Adjust the inheritance base class to virtual inheritance.

class Base7
{
    public:
        int ival;
        virtual void doit(){
            ival = 0;
            fval = 0.0;
            // dval = 0.0; //error 
            std::cout << "Base7::doit\n";
        };
    protected:
        float fval;
    private:
        double dval;
};

class Child1 : virtual public Base7    //virtual继承
{
    public:
        char cval;
    void doit(){
        ival = 1;
        fval = 1.0;
        // dval = 1.0; //error 
        std::cout << "Child1::doit\n";
    };
};
class Child2 : public virtual Base7 virtual继承
{
    public:
        char cval;
    void doit()
    {
        ival = 2;
        fval = 2.0;
        // dval = 2.0; //error 
        std::cout << "Child2::doit\n";
    };
};

class Child12 : public Child1 , public Child2
{
    public:
    void doit(){
        ival = 1;       //OK,虚继承,冲突消除
        fval = 1.0;     //OK,虚继承,冲突消除
        // dval = 1.0; //error 
        std::cout << "Child12::doit\n";
    };
};
//
    std::cout << "-------------------------\n";
    Child12 c12;
    Child1 c1;
    Child2 c2;
    Base7 b7;
    c12.doit();  //Child12::doit
    c1.doit();
    c2.doit();
    b7.doit();
    Base7& rc12 = c12;
    Base7& rc1 = c1;
    Base7& rc2 = c2;
    Base7& rb7 = b7;
    rc12.doit();
    rc1.doit();
    rc2.doit();
    rb7.doit();
//out log
Child12::doit
Child1::doit
Child2::doit
Base7::doit
Child12::doit
Child1::doit
Child2::doit
Base7::doit

        Under multiple inheritance, class scopes are more complicated because multiple base class scopes can surround derived class scopes. Typically, names and lookups used in member functions are first performed in the function itself, and if the name cannot be found locally, the lookup continues in the member's class, and then in each base class in turn. Under multiple inheritance, the lookup checks all base class inheritance subtrees at the same time. In the above example, the Base7/Child1 subtree and the Base7/Child2 subtree are looked up in parallel. If the name is found in more than one subtree, use of that name must explicitly specify which base class to use; otherwise, use of the name is ambiguous.

        13.2 In case of multiple inheritance, ensure the uniqueness of virtual function overload resolution candidates

        When a class has multiple base classes, the name lookup is performed simultaneously through all direct base classes. Derived classes of multiple inheritance may inherit members of the same name from two or more base classes, and the unqualified use of this member is ambiguous. These require some circumvention to eliminate ambiguity based on the entire inheritance tree and application needs.

class Base7
{
    public:
        int ival;
        virtual void doit(){
            ival = 0;
            fval = 0.0;
            // dval = 0.0; //error 
            std::cout << "Base7::doit\n";
        };
    protected:
        float fval;
    private:
        double dval;
};

class Child1 : virtual public Base7
{
    public:
        char cval;
    void doit(){    //如果Child1或Child2不实现基类的虚函数声明呢,或两者都不呢
        ival = 1;
        fval = 1.0;
        // dval = 1.0; //error 
        std::cout << "Child1::doit\n";
    };
};
class Child2 : public virtual Base7
{
    public:
        char cval;
    void doit()    //如果Child1或Child2不实现基类的虚函数声明呢,或两者都不呢
    {
        ival = 2;
        fval = 2.0;
        // dval = 2.0; //error 
        std::cout << "Child2::doit\n";
    };
};
//error: no unique final overrider for 'virtual void Base7::doit()' in 'Child12'
class Child12 : public Child1 , public Child2
{
    public:
    // void doit(){        //如果不实现基类的虚函数声明呢
    //     ival = 1;       //OK,虚继承,冲突消除
    //     fval = 1.0;     //OK,虚继承,冲突消除
    //     // dval = 1.0; //error 
    //     std::cout << "Child12::doit\n";
    // };
};

void virtual_first_test(void)
{
    std::cout << "-------------------------\n";
    Child12 c12;
    Child1 c1;
    Child2 c2;
    Base7 b7;
    c12.doit();
    c1.doit();
    c2.doit();
    b7.doit();
    Base7& rc12 = c12;
    Base7& rc1 = c1;
    Base7& rc2 = c2;
    Base7& rb7 = b7;
    /*
    error: request for member 'doit' is ambiguous
    note: candidates are: 'virtual void Child2::doit()'
    note: candidates are: 'virtual void Child1::doit()'
    */
    rc12.doit();//
    rc1.doit();
    rc2.doit();
    rb7.doit();
};

        In the above code, if the child12 derived class does not implement the doit virtual function, an exception will occur in compilation, because there is ambiguity when referencing the doit function, and it is impossible to determine whether the doit function of Child1 or Child2 is called, because their candidate priority unanimous. However, regardless of whether the derived class of Child12 implements virtual functions, if one of Child1 or Child2 does not implement virtual functions, then there is only one candidate chain when the Child12 object is called, Child12->Child1->Base7 or Child12->Child2->Base7, which is eliminated The ambiguity is eliminated; if neither implements virtual functions, there is only one candidate chain, Child12->Base7. Of course, if the Child1 or Child2 derived class is not virtual inheritance, it will also bring ambiguity.

14. Runtime type identification (RTTI) and virtual functions

        14.1 Run-Time Type Identification (RTTI)

        Through run-time type identification (RTTI), programs are able to use pointers or references of base classes to retrieve the actual derived type of the object to which those pointers or references refer.
        RTTI is provided through the following two operators:
        1. The typeid operator returns the actual type of the object pointed to by the pointer or reference.
        2. The dynamic_cast operator safely converts a pointer or reference of a base class type to a pointer or reference of a derived type.

         These operators return dynamic type information only for classes with one or more virtual functions, and static (i.e., compile-time) type information for other types. For classes with virtual functions, the RTTI operator is executed at runtime, but for other types, the RTTI operator is evaluated at compile time.

        14.2 dynamic_cast operator and virtual functions

        When you have a base class reference or pointer, but you need to perform derived class operations that are not part of the base class, dynamic casts are required. Often, the best way to get derived class behavior from base class pointers is through virtual functions. When using virtual functions, the compiler automatically chooses the correct function based on the actual type of the object.

class Base7
{
    public:
        int ival;
        virtual void doit(){
            ival = 0;
            fval = 0.0;
            // dval = 0.0; //error 
            std::cout << "Base7::doit\n";
        };
    protected:
        float fval;
    private:
        double dval;
};

class Child3 : public Base7
{
    public:
        char cval;
    void doit() //如果Child1或Child2不实现基类的虚函数声明呢,或两者都不呢
    {
        ival = 2;
        fval = 2.0;
        // dval = 2.0; //error 
        std::cout << "Child2::doit\n";
    };
    void f(){std::cout << "Child1::f()\n";};
};
void dynamic_test(void)
{
    try
    {
        Base7 b7;
        //当转换失败的时候,它抛出一个 std::bad_cast 异常,该异常在库头文件 typeinfo 中定义
        // Child3& rc3 = dynamic_cast<Child3&>(b7);//error,std::bad_cast
        std::cout << "------------1-------------\n";
        Base7 *pb7 = new Base7();
        // Child3* pc3 = dynamic_cast<Child3*>(&b7);   //OK,warning: 'dynamic_cast<class Child3*>(Base7 b7)' can never succeed
        Child3* pc3 = dynamic_cast<Child3*>(pb7);   //OK
        // pc3->doit();    //error,error
        pc3->f();       //OK
        pc3 = nullptr;
        delete pb7;
        pb7 = nullptr;
        std::cout << "------------2-------------\n";
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << '\n';
    }catch(...)
    {
        std::cout << "error\n";
    }
}

        Use the dynamic_cast operator to convert a reference or pointer to an object of a base class type to a reference or pointer to another type in the same inheritance hierarchy. The pointer used with dynamic_cast must be valid, it must be 0 or point to an object.
        dynamic_cast involves runtime type checking. dynamic_cast fails if the object bound to the reference or pointer is not an object of the target type. If the dynamic_cast to the pointer type fails, the result of the dynamic_cast is the value 0; if the dynamic_cast to the reference type fails, an exception of type bad_cast is thrown.
        Therefore, the dynamic_cast operator performs two operations at a time. It first verifies that the requested conversion is valid, and only then does the operator actually perform the conversion. Generally speaking, the type of the object bound by the reference or pointer is unknown at compile time. The pointer of the base class can be assigned to point to the derived class object. Similarly, the reference of the base class can also be initialized with the derived class object. The dynamic_cast operator Validation performed must occur at runtime.
        However, in some cases it is not possible to use virtual functions. In these cases, RTTI provides optional mechanisms. However, this mechanism is more error-prone than using virtual functions: the programmer must know to which type the object should be cast, and must check that the conversion was performed successfully. Be careful with dynamic casts. Whenever possible, it is much better to define and use virtual functions than to take over type management directly.

        14.3 The typeid operator and virtual functions

        The typeid operator, the typeid operator enables a program to ask an expression: what type are you? Usage is typeid(expression). The dynamic type of an expression may differ from its static compile-time type if the type of the expression is a class type and that class contains one or more virtual functions.

        For example, if an expression dereferences a base class pointer, the expression's static compile-time type is the base class type; however, if the pointer actually points to a derived class object, the typeid operator will say that the expression's type is the derived type.
        The typeid operator can be used with any type of expression. Both expressions of built-in types as well as constants can be used as operands of the typeid operator. If the operand is not a class type or a class without virtual functions, the typeid operator indicates the static type of the operand; if the operand is a class type that defines at least one virtual function, the type is calculated at runtime.
        The result of the typeid operator is an object reference to a standard library type named type_info. To use the type_info class, the library header file typeinfo must be included.

class Base7
{
    public:
        int ival;
        virtual void doit(){
            ival = 0;
            fval = 0.0;
            // dval = 0.0; //error 
            std::cout << "Base7::doit\n";
        };
    protected:
        float fval;
    private:
        double dval;
};

class Child3 : public Base7
{
    public:
        char cval;
    void doit() //如果Child1或Child2不实现基类的虚函数声明呢,或两者都不呢
    {
        ival = 2;
        fval = 2.0;
        // dval = 2.0; //error 
        std::cout << "Child2::doit\n";
    };
    void f(){std::cout << "Child1::f()\n";};
};

#include <typeinfo>
void typeid_test(void)
{
    try
    {
    Base7 b7;
    Child3 c3;
    if (typeid(Base7) == typeid(Child3)) {
        std::cout << "Base7 and Child3 objects of the same type\n";
    }else{
        std::cout << "b7 and c3 objects of unsame type\n";
    }
    if (typeid(b7) == typeid(c3)) {
        std::cout << "b7 and c3 objects of the same type\n";
    }else{
        std::cout << "b7 and c3 objects of unsame type\n";
    }
    Base7& rc3 = c3;    //基类引用,指向派生类引用
    if (typeid(rc3) == typeid(c3)) {
        std::cout << "rc3 and c3 objects of the same type\n";
    }else{
        std::cout << "rc3 and c3 objects of unsame type\n";
    }
    Base7 *pb7=new Base7();
    Child3 *pc3 = dynamic_cast<Child3*>(pb7);
    if (typeid(pb7) == typeid(pc3)) {
        std::cout << "pb7 and pc3 objects of the same type\n";
    }else{
        std::cout << "pb7 and pc3 objects of unsame type\n";
    }
    // if (typeid(*pb7) == typeid(*pc3)) { //error,std::bad_typeid
    //     std::cout << "*pb7 and *pc3 objects of the same type\n";
    // }else{
    //     std::cout << "*pb7 and *pc3 objects of unsame type\n";
    // }
    Child3 *pc3l = new Child3();
    Base7* pbc3 = dynamic_cast<Base7*>(pc3l);
    if (typeid(pbc3) == typeid(pc3l)) {
        std::cout << "pbc3 and pc3l objects of the same type\n";
    }else{
        std::cout << "pbc3 and pc3l objects of unsame type\n";
    }
    Base7* pbc3l = &c3;    //基类指针,指向派生类应用
    if (typeid(*pc3l) == typeid(c3)) {
        std::cout << "*pc3l and c3 objects of the same type\n";
    }else{
        std::cout << "*pc3l and c3 objects of unsame type\n";
    }
    delete pb7;
    pb7 = nullptr;
    delete pc3;
    pc3 = nullptr;
    pbc3l = nullptr;
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << '\n';
    }
};
//out log
b7 and c3 objects of unsame type
b7 and c3 objects of unsame type
rc3 and c3 objects of the same type
pb7 and pc3 objects of unsame type
pbc3 and pc3l objects of unsame type
*pc3l and c3 objects of the same type

        Dynamic type information is returned only when the operand of typeid is an object of class type with virtual functions. Testing a pointer (relative to the object pointed to by the pointer) returns the static, compile-time type of the pointer.

        Run-time type identification (RTTI) provides language-level support for this type of programming. RTTI applies only to classes that define virtual functions, type information for types that do not define virtual functions is available but reflects static typing.

15. Complete source code

        Compilation instructions: g++ main.cpp test1.cpp -o test.exe -std=c++11

        main.cpp

#include "test1.h"

int main(int argc, char* argv[])
{
    virtual_first_test();
    return 0;
}

        test1.h

#ifndef _TEST_1_H_
#define _TEST_1_H_

void virtual_first_test(void);

#endif //_TEST_1_H_

        test1.cpp

#include "test1.h"

#include <iostream>
class Base {
    public:
    virtual void f() {
        std::cout << "Base\n";
    }
    void g(){
        std::cout << "Base g()\n";
    };
    virtual void func(int val_=1) {
        std::cout << "Base::func("<< val_<<")\n";
    }
};

class Derived : public Base {
    public:
    void f() override { // 'override' 可选,覆盖,Base::f()
        std::cout << "Derived\n";
    }
    void g(){   //被覆盖,无效
        std::cout << "Base g()\n";
    };
    void func(int val_=10) {
        std::cout << "Derived::func("<< val_<<")\n";
    }
};

class A_override
{
public:
    virtual void foo();
    void bar();
};

class B_override : public A_override
{
public:
    // void foo() const override; // 错误:B::foo 不覆盖 A::foo,签名不匹配
    void foo() override; // OK:B::foo 覆盖 A::foo
    // void bar() override; // 错误:A::bar 非虚
};

class A_final
{
public:
    virtual void foo();
};
 
class B_final : A_final
{
public:
    void foo() final; // A_final::foo 被覆盖而 B_final::foo 是最终覆盖函数
    // void bar() final; // 错误: bar 非虚,因此它不能是 final 的
};
 
class C_final final : B_final // class C_final 为 final
{
public:
    // void foo() override; // 错误:foo 不能被覆盖,因为它在 B_final 中是 final 的
};
 
// class D_final : C_final // 错误:C_final 是 final 的
// {
//     public:
// };

class DDerived1 : virtual public Derived {  //虚继承
    public:
    void f() override { // 'override' 可选,覆盖Base::f()
        std::cout << "DDerived2\n";
    }
};

class DDerived2 : public Derived {
    public:
    void f() override { // 'override' 可选,覆盖Derived::f()
        std::cout << "DDerived2\n";
    }
};

class DDerived3 : virtual public Derived {//虚继承,不引入覆盖函数,最终覆盖函数是Derived::f()
    public:
};

class DDDerived : public DDerived1,DDerived3 {};//不引入覆盖函数,最终覆盖函数是 DDerived1::f

class Derived_1 : public Base {
    public:
    void mufunc()
    {
        std::cout << "Derived_1::mufunc\n";
    }
    void func(int val_) {
        std::cout << "Derived_1::func("<< val_<<")\n";
    }
};

class Derived_2 : public Base {
    public:
    void f()
    {
        Base::f();
        std::cout << "Derived_2::f\n";
    }
};

class Base1
{
private:
    /* data */
    char *pstr;
public:
    Base1(int size=10);
    virtual ~Base1();
    void f();
};

Base1::Base1(int size)
{
    // f();    //error
    pstr = (char*)malloc(size*sizeof(char));
    std::cout << "Base1\n";
}

Base1::~Base1()
{
    f();    //OK
    delete[] pstr;
    pstr = nullptr;
    std::cout << "~Base1\n";
}

void Base1::f()
{
    char str[] = "hello";
    memcpy(pstr,str,sizeof(str));
    std::cout << "Base1::f\n";
};

class Derived_3 : public Base1 {
    public:
    Derived_3(){
        f();
        std::cout << "Derived_3\n";
    };
    ~Derived_3(){
        f();
        std::cout << "~Derived_3\n";
    };
    void f(){std::cout << "Derived_3::f\n";};
    //或采用默认构造、析构
    void func(){
        std::cout << "Derived_3::func\n";
        f();
    }
};

class Derived_4 : public Derived_3 {
    public:
    Derived_4(){
        f();
        std::cout << "Derived_4\n";
    };
    ~Derived_4(){
        f();
        std::cout << "~Derived_4\n";
    };  
    void f(){std::cout << "Derived_4::f\n";};
};

void destructorTest(void)
{
    try{
        std::cout << "------------------\n";
        Derived_3 *pd3 = new Derived_3();
        pd3->func();
        delete pd3;
        std::cout << "------------------\n";
        Derived_4 d4;
        d4.func();
        std::cout << "------------------\n";
        Base1& b1 = d4;
        b1.f();
        std::cout << "------------------\n";
        Derived_4 *pd4 = new Derived_4();
        delete pd4;
        std::cout << "------------------\n";
    }catch(...)
    {
        std::cout << "Derived_3::mem\n";
    }
}

class Base2
{
public:
    virtual ~Base2(){}; //作为根类,最好给予virtual声明定义
};

class Derived_5 : public Base2 
{
    private:
    char* pstr;
    public:
    Derived_5(){pstr = (char*)malloc(10*sizeof(char));}
    ~Derived_5(){delete[] pstr; pstr=nullptr;}  //实际为virtual函数
};

class Base3
{
private:
    char* pstr;
public:
    Base3(){pstr = (char*)malloc(10*sizeof(char));};
    // virtual Base3(){pstr = (char*)malloc(10*sizeof(char));};//error,constructors cannot be declared 'virtual'
    virtual ~Base3(){delete[] pstr; pstr=nullptr;}; //作为根类,最好给予virtual声明定义
    // virtual Base3& operator=(const Base3& obj);  //可定义,但无意义
    Base3& operator=(const Base3& obj){
        if (this== &obj)
        {
            return *this;
        }
        delete[] pstr;
        int size = sizeof(obj.pstr);
        pstr = (char*)malloc(size);
        memcpy(pstr,obj.pstr,size);
        return *this;
    };
};

class Derived_6 : public Base3 {};

class Base4
{
private:
    /* data */
public:
    Base4(/* args */){};
    virtual ~Base4(){};
    virtual void f(void){std::cout << "Base4\n";};
};

class Derived_7 : public Base4 {
    public:
    void f(int val=0){std::cout << "Derived_7("<<val<<")\n";};//实参不同,屏蔽了基类虚函数实现
};

class Derived_7l : public Base4 {
    public:
    // int f(void){std::cout << "Derived_7l\n"; return 1;};//error,参数及函数名一致,返回类型不同,与基类函数引起冲突
};

class Derived_8 : public Derived_7 {
    public:
    void f(void){std::cout << "Derived_8\n";};//重新对基类虚函数声明进行虚实现
};

void virtual_shield_test(void)
{
    Base4 b4;
    Derived_7 d7;
    Derived_8 d8;
    b4.f(); // 打印 "Base4"
    d7.f(); // 打印 "Derived_7(0)"
    d7.Base4::f();// 打印 "Base4"
    d8.f(); // 打印 "Derived_8"
    Base4& rb4 = b4;
    Base4& rd7 = d7;
    Base4& rd8 = d8;
    rb4.f();    // 打印 "Base4"
    rd7.f();    // 打印 "Base4",通过Base4引用派生类,调用虚函数并不能匹配到派生类的虚实现,又回退调用基类虚实现
    rd8.f();    // 打印 "Derived_8"
};

class Base5
{
private:
    /* data */
public:
    Base5(/* args */){};
    virtual ~Base5(){};
    virtual void f(void) = 0 ;
};

class Derived_9 : public Base5 {
    public:
    void f(void){std::cout << "Derived_9\n";};
};

class Derived_10 : public Base5 {
    public:
};

void pure_virtual_test(void)
{
    // Base5 b5;//error
    Derived_9 d9;
    d9.f();
    Base5& rd9 = d9;
    rd9.f();    
    // Derived_10 d10; //error,纯虚函数必须在派生类虚实现
};

class Base6 {
public:
    virtual double f1(double) noexcept{return 1.0;};
    virtual int f2(int) noexcept(false){ return 1;};
    virtual std::string f3(char* ps) noexcept(false){ return "";};
};

class Derived_11 : public Base6 {
public:
    // error,looser exception specification on overriding virtual function 'virtual double Derived_11::f1(double) noexcept (false)
    // double f1(double) noexcept(false){ return 1.0;};
    // ok: 与基类保持一致
    int f2(int) noexcept(false){ return 1;};
    // ok: 比基类更严格
    std::string f3(char* ps) noexcept{ return "";};
};

class Base7
{
    public:
        int ival;
        virtual void doit(){
            ival = 0;
            fval = 0.0;
            // dval = 0.0; //error 
            std::cout << "Base7::doit\n";
        };
    protected:
        float fval;
    private:
        double dval;
};

class Child1 : virtual public Base7
{
    public:
        char cval;
    void doit() //如果Child1或Child2不实现基类的虚函数声明呢,或两者都不呢
    {
        ival = 1;
        fval = 1.0;
        // dval = 1.0; //error 
        std::cout << "Child1::doit\n";
    };
};
class Child2 : public virtual Base7
{
    public:
        char cval;
    void doit() //如果Child1或Child2不实现基类的虚函数声明呢,或两者都不呢
    {
        ival = 2;
        fval = 2.0;
        // dval = 2.0; //error 
        std::cout << "Child2::doit\n";
    };
};

class Child12 : public Child1 , public Child2
{
    public:
    void doit(){        //如果不实现基类的虚函数声明呢
        ival = 1;       //OK,虚继承,冲突消除
        fval = 1.0;     //OK,虚继承,冲突消除
        // dval = 1.0; //error 
        std::cout << "Child12::doit\n";
    };
};

class Child3 : public Base7
{
    public:
        char cval;
    void doit() //如果Child1或Child2不实现基类的虚函数声明呢,或两者都不呢
    {
        ival = 2;
        fval = 2.0;
        // dval = 2.0; //error 
        std::cout << "Child2::doit\n";
    };
    void f(){std::cout << "Child1::f()\n";};
};
void dynamic_test(void)
{
    try
    {
        Base7 b7;
        //当转换失败的时候,它抛出一个 std::bad_cast 异常,该异常在库头文件 typeinfo 中定义
        // Child3& rc3 = dynamic_cast<Child3&>(b7);//error,std::bad_cast
        std::cout << "------------1-------------\n";
        Base7 *pb7 = new Base7();
        // Child3* pc3 = dynamic_cast<Child3*>(&b7);   //OK,warning: 'dynamic_cast<class Child3*>(Base7 b7)' can never succeed
        Child3* pc3 = dynamic_cast<Child3*>(pb7);   //OK
        // pc3->doit();    //error,error
        pc3->f();       //OK
        pc3 = nullptr;
        delete pb7;
        pb7 = nullptr;
        std::cout << "------------2-------------\n";
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << '\n';
    }catch(...)
    {
        std::cout << "error\n";
    }
}

#include <typeinfo>
void typeid_test(void)
{
    try
    {
    Base7 b7;
    Child3 c3;
    if (typeid(Base7) == typeid(Child3)) {
        std::cout << "Base7 and Child3 objects of the same type\n";
    }else{
        std::cout << "b7 and c3 objects of unsame type\n";
    }
    if (typeid(b7) == typeid(c3)) {
        std::cout << "b7 and c3 objects of the same type\n";
    }else{
        std::cout << "b7 and c3 objects of unsame type\n";
    }
    Base7& rc3 = c3;    //基类引用,指向派生类引用
    if (typeid(rc3) == typeid(c3)) {
        std::cout << "rc3 and c3 objects of the same type\n";
    }else{
        std::cout << "rc3 and c3 objects of unsame type\n";
    }
    Base7 *pb7=new Base7();
    Child3 *pc3 = dynamic_cast<Child3*>(pb7);
    if (typeid(pb7) == typeid(pc3)) {
        std::cout << "pb7 and pc3 objects of the same type\n";
    }else{
        std::cout << "pb7 and pc3 objects of unsame type\n";
    }
    // if (typeid(*pb7) == typeid(*pc3)) { //error,std::bad_typeid
    //     std::cout << "*pb7 and *pc3 objects of the same type\n";
    // }else{
    //     std::cout << "*pb7 and *pc3 objects of unsame type\n";
    // }
    Child3 *pc3l = new Child3();
    Base7* pbc3 = dynamic_cast<Base7*>(pc3l);
    if (typeid(pbc3) == typeid(pc3l)) {
        std::cout << "pbc3 and pc3l objects of the same type\n";
    }else{
        std::cout << "pbc3 and pc3l objects of unsame type\n";
    }
    Base7* pbc3l = &c3;    //基类指针,指向派生类应用
    if (typeid(*pc3l) == typeid(c3)) {
        std::cout << "*pc3l and c3 objects of the same type\n";
    }else{
        std::cout << "*pc3l and c3 objects of unsame type\n";
    }
    delete pb7;
    pb7 = nullptr;
    delete pc3;
    pc3 = nullptr;
    pbc3l = nullptr;
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << '\n';
    }
};

void virtual_first_test(void)
{
    Base b;
    Derived d;
    b.f();  // 打印 "Base"
    d.f();  // 打印 "Derived"
    b.g();  //打印 "Base g()"
    d.g();  //打印 "Base g()"
    d.Derived::g();//打印 "Base g()"
    // 通过引用调用虚函数
    Base& br = b; // br 的类型是 Base&
    Base& dr = d; // dr 的类型也是 Base&
    br.f(); // 打印 "Base"
    dr.f(); // 打印 "Derived"
    br.g(); // 打印 "Base g()"
    dr.g(); // 打印 "Base g()"
 
    // 通过指针调用虚函数
    Base* bp = &b; // bp 的类型是 Base*
    Base* dp = &d; // dp 的类型也是 Base*
    bp->f(); // 打印 "Base"
    dp->f(); // 打印 "Derived"
    bp->g(); // 打印 "Base g()"
    dp->g(); // 打印 "Base g()"
 
    // 非虚函数调用
    br.Base::f(); // 打印 "Base"
    dr.Base::f(); // 打印 "Base"
    Derived* dp1 = &d;
    dp1->Derived::f();// 打印 "Derived"
    //
    Base bobj;
    Derived_1 dobj;
    bobj.f();   // 打印 "Base"
    dobj.f();   // 打印 "Base"
    Base* bpobj = &bobj; // bp 的类型是 Base*
    Base* dpobj = &dobj; // dp 的类型也是 Base*
    bpobj->f(); // 打印 "Base"
    dpobj->f(); // 打印 "Base"
    //
    Derived_2 dobj2;
    dobj2.f();  // 打印 "Base和Derived_2::f"
    Base* dpobj2 = &dobj2; // dp 的类型也是 Base*
    dpobj2->f();// 打印 "Base和Derived_2::f"
    //
    Base barg;
    Derived darg;
    Derived_1 darg1;
    Base& pbarg = barg;
    Base& pdarg = darg;
    Base& pdarg1 = darg1;
    pbarg.func();   //Base::func(1)
    pdarg.func();   //Derived::func(1)
    pdarg1.func();  //Derived_1::func(1)
    darg.func();    //Derived::func(10)
    //
    destructorTest();
    virtual_shield_test();
    pure_virtual_test();
    //
    std::cout << "-------------------------\n";
    Child12 c12;
    Child1 c1;
    Child2 c2;
    Base7 b7;
    c12.doit();  //Child12::doit
    c1.doit();
    c2.doit();
    b7.doit();
    Base7& rc12 = c12;
    Base7& rc1 = c1;
    Base7& rc2 = c2;
    Base7& rb7 = b7;
    rc12.doit();
    rc1.doit();
    rc2.doit();
    rb7.doit();
    //
    dynamic_test();
    typeid_test();
};

Guess you like

Origin blog.csdn.net/py8105/article/details/129615179