深入而全面了解virtual虚函数与类设计关系

目录

一、virtual 函数说明符的作用

二、virtual 成员函数与普通成员函数

三、不必每个派生类重定义virtual 函数

四、派生类可以兼顾基类virtual 函数能力

五、覆盖虚函数机制

5.1 virtual覆盖机制

5.2 虚函数覆盖与继承方式、派生行为等相关

5.3 override说明符

5.4 final说明符

六、虚函数与默认实参

七、虚析构函数

7.1 确保整个继承体系有效释放资源

7.2 为根基类保持虚析构是好习惯

八、 构造函数和赋值操作符不能声明虚函数

8.1 禁止构造函数为虚函数

8.2 赋值操作符声明为虚函数无意义

九、构造函数和析构函数中的虚函数

9.1 按构造及析构顺序谨慎处理虚函数

9.2 构造或析构函数内调用虚函数主要类型完整性

十、虚函数屏蔽

十一、纯虚函数

十二、异常说明与虚函数

十三、多重继承的虚函数

13.1 虚继承规避基类二义性

13.2 多继承时,确保虚函数候重载决议候选唯一性

十四、运行时类型识别(RTTI)与虚函数

14.1运行时类型识别(RTTI)

14.2 dynamic_cast 操作符与虚函数

14.3 typeid 操作符与虚函数


一、virtual 函数说明符的作用

virtual 说明符指定非静态成员函数为虚函数并支持动态调用派发。它只能在非静态成员函数的首个声明(即当它在类定义中声明时)的 声明说明符序列中出现。在 C++ 中,基类必须指出希望派生类重写哪些函数,定义为 virtual 的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数

#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"
};

在 C++ 中,通过基类的引用(或指针)调用虚函数时,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。当使用到基类的指针或引用来处理派生类时,对被覆盖的虚函数的调用,将会调用定义于派生类中的行为。这种函数调用被称为虚函数调用或虚调用。虚函数调用在使用有限定名字查找(即函数名出现在作用域解析运算符 :: 的右侧)时被抑制。

虚函数是其行为可以在派生类中覆盖的成员函数。如果过引用或指针调用虚函数时,编译器将生成代码,直到运行时才能确定调用哪个函数,运行的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本。

非成员函数和静态成员函数不能是虚函数函数模板不能被声明为 virtual,这只适用于自身是模板的函数。而类模板的常规成员函数可以被声明为虚函数。

二、virtual 成员函数与普通成员函数

对于基类普通成员函数,在派生类声明一致(相同名称及形参列表)的成员函数时,派生类的成员函数会被基类成员函数覆盖而无法生效,即如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。

#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"
};

上述代码展示,C++ 中的函数调用默认不使用动态绑定。

要触发动态绑定,满足两个条件:

第一,只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定;

第二,必须通过基类类型的引用或指针进行函数调用。

三、不必每个派生类重定义virtual 函数

基类通常应将派生类需要重定义的任意函数定义为虚函数。尽管不是必须这样做,派生类一般会重定义所继承的虚函数。派生类没有重定义某个虚函数,则使用基类中定义的版本。

#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"
};

如果需要在派生类中实现与基类虚函数不一样的功能时,派生类型必须对想要重定义的每个继承成员进行声明。

四、派生类可以兼顾基类virtual 函数能力

派生类中的虚函数实现中,除了实现自身行为,也可以显示调用基类函数。

#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"
};

一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实。派生类重定义虚函数时,可以使用 virtual 保留字,但不是必须这样做。

五、覆盖虚函数机制

上述代码体现了覆盖虚函数机制。

5.1 virtual覆盖机制

在某些情况下,希望覆盖虚函数机制并强制函数调用使用虚函数的特定版本,可以使用作用域操作符。

#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",覆盖机制
};

覆盖机制最常见的用法就是为了派生类虚函数调用基类中的版本。在这种情况下,基类版本可以完成继承层次中所有类型的公共任务,而每个派生类型只添加自己的特殊工作。PS:派生类虚函数调用基类版本时,必须显式使用作用域操作符。如果派生类函数忽略了这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归。

5.2 虚函数覆盖与继承方式、派生行为等相关

每个虚函数都有最终覆盖函数,它是进行虚函数调用时所执行的函数。基类 Base 的虚成员函数 f是最终覆盖函数,除非派生类声明或(通过多重继承)继承了覆盖 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

关于虚函数的覆盖机制还需要注意的是:

  • 如果一个函数拥有多于一个最终覆盖函数,那么程序非良构。
  • 如果函数以说明符 override 声明,但不覆盖任何虚函数,那么程序非良构。
  • 如果函数以说明符 final 声明,而被另一函数试图覆盖,那么程序非良构。

5.3 override说明符

override说明符(C++11 起),指定一个虚函数覆盖另一个虚函数,其语法表现:

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

在成员函数的声明或定义中,override 说明符确保该函数为虚函数并覆盖某个基类中的虚函数。如果不是这样,那么程序为谬构(生成编译错误)。

值得注意的是: override 是在成员函数声明符之后使用时拥有特殊含义的标识符,其他情况下它不是保留的关键词。

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说明符

final说明符 (C++11 起),某个虚函数不能在派生类中被覆盖,或者某个类不能被派生,其语法表现:

/*当应用到成员函数时,标识符final在类定义中的成员函数声明或成员函数定义的语法中
*,紧随声明符之后出现。当应用到类时,标识符final在类定义的开头,紧跟类名之后出现。*//*格式【1】:
*在成员函数声明中,final可以在紧跟声明符之后的虚说明符序列中出现,如果有使用纯说明符则在其之前。*/
声明符 虚说明符序列(可选) 纯说明符(可选) ///*格式【2】:
*在类定义内的成员函数定义中,final可以在紧跟声明符之后并紧接函数体之前的虚说明符序列中出现。*/
声明符 虚说明符序列(可选) 函数体 ///*格式【3】:
*在类定义中,final可以在紧跟类名之后,紧接基类子句(若使用它)起头的冒号之前,
*作为类虚说明符 出现。*/
类关键词 attr(可选) 类头名 类虚说明符(可选) 基类子句(可选) // /*格式(1,2) 中,如果有使用虚说明符序列,那么它是 override、final、final override
*或 override final 之一。情况 (3) 中,如果有使用类虚说明符则只允许final。*/

final 说明符使用注意事项:

当在虚函数声明或定义中使用时,final 说明符确保函数为虚并指定其不可被派生类覆盖,否则程序为谬构(生成编译时错误);

当在类定义中使用时,final 指定此类不可在另一类的定义中的 基类说明符列表 中出现(换言之,不能派生于它),否则程序非良构(生成编译时错误);

final 也可以用于联合体定义,此时下它没有效果(除了 std::is_final 的输出结果) (C++14 起),因为不能从联合体派生;

final 是在成员函数声明或类头部中使用时有特殊含义的标识符。其他语境中它未被保留,而且可用于命名对象或函数。

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:
// };

六、虚函数与默认实参

像其他任何函数一样,虚函数也可以有默认实参。通常,如果有用在给定调用中的默认实参值,该值将在编译时确定。如果一个调用省略了具有默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关。通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。

#include <iostream>
class Base {public:virtual void func(int val_=1) {//默认实参=1std::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)
};

在同一虚函数的基类版本和派生类版本中使用不同的默认实参几乎一定会引起麻烦。如果通过基类的引用或指针调用虚函数,但实际执行的是派生类中定义的版本,这时就可能会出现问题。在这种情况下,为虚函数的基类版本定义的默认实参将传给派生类定义的版本,而派生类版本是用不同的默认实参定义的。

七、虚析构函数

7.1 确保整个继承体系有效释放资源

如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为。要保证运行适当的析构函数,基类中的析构函数必须为虚函数。

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

虚析构函数对自动调用基类部分的析构函数对基类的设计有重要影响。删除指向动态分配对象的指针时,需要运行析构函数在释放对象的内存之前清除对象。处理继承层次中的对象时,指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针。

7.2 为根基类保持虚析构是好习惯

如果析构函数为虚函数,那么通过指针调用时,运行哪个析构函数将因指针所指对象类型的不同而不同。无论是基类还是派生类,如果成员变量存在动态分配内存的,最好将析构函数声明为virtual。像其他虚函数一样,析构函数的虚函数性质都将继承。因此,如果层次中根类的析构函数为虚函数,则派生类析构函数也将是虚函数,无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。

如果类需要析构函数,则类几乎也确实需要其他复制控制成员。基类几乎总是需要构造函数,从而可以将析构函数设为虚函数。

如果基类为了将析构函数设为虚函数则具有空析构函数,那么,类具有析构函数并不表示也需要赋值操作符或复制构造函数。即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。

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.1 禁止构造函数为虚函数

构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。虽然可以在基类中将成员函数 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声明定义
};class Derived_6 : public Base3 {};

8.2 赋值操作符声明为虚函数无意义

将赋值操作符operator=设为虚函数可能会令人混淆,而且不会有什么用处,因为虚函数必须在基类和派生类中具有同样的形参。基类赋值操作符有一个形参是自身类类型的引用,如果该操作符为虚函数,则每个类都将得到一个虚函数成员,该成员定义了参数为一个基类对象的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.1 按构造及析构顺序谨慎处理虚函数

构造派生类对象时首先运行基类构造函数初始化对象的基类部分。在执行基类构造函数时,对象的派生类部分是未初始化的。实际上,此时对象还不是一个派生类对象。撤销派生类对象时,首先撤销它的派生类部分,然后按照与构造顺序的逆序撤销它的基类部分。

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 构造或析构函数内调用虚函数主要类型完整性

在这两种情况下,运行构造函数或析构函数的时候,对象都是不完整的。为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。构造或析构期间的对象类型对虚函数的绑定有影响。如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。无论由构造函数(或析构函数)直接调用虚函数,或者从构造函数(或析构函数)所调用的函数间接调用虚函数,都应用这种绑定。

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

要理解这种行为,考虑如果从基类构造函数(或析构函数)调用虚函数的派生类版本会怎么样。虚函数的派生类版本很可能会访问派生类对象的成员,如果派生类版本不需要使用派生类对象的成员,派生类多半能够使用基类中的定义。但是,对象的派生部分的成员不会在基类构造函数运行期间初始化,实际上,如果允许这样的访问,程序很可能会崩溃。

十、虚函数屏蔽

虚函数必须在基类和派生类中拥有同一原型了。如果基类成员与派生类成员接受的实参不同,就没有办法通过基类类型的引用或指针调用派生类函数。

  • 虽然名字相同但形参列表不同的函数并不覆盖同名的基类函数,但会隐藏它:在无限定名字查找检查派生类的作用域时,查找找到该声明就不会再检查基类。
  • 如果名字相同和形参列表不同,但是返回类型不一致,就会引起编译错误告警。
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"
};

Derived_7中的 f版本没有重定义 Base4 的虚函数 f,相反,它屏蔽了基类的f。结果 Derived_7有两个名为 f 的函数:类从 Base 继承了一个名为 f的虚函数,类又定义了自己的名为 f的非虚成员函数,该函数接受一个 int 形参。但是,从 Base4 继承的虚函数不能通过 Derived_7对象(或 Derived_7的引用或指针)调用,因为该函数被 f(int) 的定义屏蔽了。因此,通过基类类型Base4 的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类Derived_7的实现。

类 Derived_8重定义了它继承的两个函数,它重定义了 Base4 中定义的 f 的原始版本并重定义了 Derived_7中定义的非虚版本。

十一、纯虚函数

在虚函数形参表后面写上 = 0 以指定纯虚函数。将函数定义为纯虚能够说明,该函数为后代类型提供了可以覆盖的接口,但是这个类中的版本决不会调用。重要的是,用户将不能创建 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;//errorDerived_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() 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 "";};
};

派生类中 f1 的声明是错误的,因为它的异常说明在基类 f1 版本列出的异常中增加了一个异常。派生类不能在异常说明列表中增加异常,原因在于,继承层次的用户应该能够编写依赖于该说明列表的代码。如果通过基类指针或引用进行函数调用,那么,这些类的用户所涉及的应该只是在基类中指定的异常。通过派生类抛出的异常限制为由基类所列出的那些,在编写代码时就可以知
道必须处理哪些异常。

即代码可以依赖于这样一个事实:基类中的异常列表是虚函数的派生类版本可以抛出的异常列表的超集

noexcept 说明符 (C++11 起),指定函数是否抛出异常:

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

十三、多重继承的虚函数

13.1 虚继承规避基类二义性

类可以是直接继承多个基类的派生类,多个基类继承时,需要注意成员名冲突问题,在应用时,成员名冲突会引发歧义,编译器无法决议而无法通过编译,这时需要将继承基类调整为virtual继承。

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::doitc1.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

在多重继承下,类作用域更加复杂,因为多个基类作用域可以包围派生类作用域。通常,成员函数中使用的名字和查找首先在函数本身进行,如果不能在本地找到名字,就继续在成员的类中查找,然后依次查找每个基类。在多重继承下,查找同时检察所有的基类继承子树,在上述例子中,并行查找Base7/Child1子树和 Base7/Child2子树。如果在多个子树中找到该名字,则那个名字的使用必须显式指定使用哪个基类;否则,该名字的使用是二义性的。

13.2 多继承时,确保虚函数候重载决议候选唯一性

当一个类有多个基类的时候,通过所有直接基类同时进行名字查找。多重继承的派生类有可能从两个或多个基类继承同名成员,对该成员不加限定的使用是二义性的。这些,需要依据整个继承树及应用需要,作出一些规避来消除二义性。

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 ambiguousnote: candidates are: 'virtual void Child2::doit()'note: candidates are: 'virtual void Child1::doit()'*/rc12.doit();//rc1.doit();rc2.doit();rb7.doit();
};

上述代码中,如果Child12派生类不实现doit虚函数的话,编译将会发生异常,那是因为在引用doit函数时发生歧义性,无法确定是Child1还是Child2的doit函数获得调用,因为他们候选优先级一致。但是,不管Child12派生类是否实现虚函数,如果Child1或Child2其中一个不实现虚函数,那么Child12对象调用时只有一个候选链,Child12->Child1->Base7或Child12->Child2->Base7,就消除了歧义;如果两者都不实现虚函数,则也只有一个候选链,Child12->Base7。当然,如果Child1或Child2派生类如果不是virtual继承的话,同样会带来歧义。

十四、运行时类型识别(RTTI)与虚函数

14.1运行时类型识别(RTTI)

通过运行时类型识别(RTTI),程序能够使用基类的指针或引用来检索这些指针或引用所指对象的实际派生类型。
        通过下面两个操作符提供 RTTI:
        1. typeid 操作符,返回指针或引用所指对象的实际类型。
        2. dynamic_cast 操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。

这些操作符只为带有一个或多个虚函数的类返回动态类型信息,对于其他类型,返回静态(即编译时)类型的信息。对于带虚函数的类,在运行时执行 RTTI 操作符,但对于其他类型,在编译时计算 RTTI 操作符。

14.2 dynamic_cast 操作符与虚函数

当具有基类的引用或指针,但需要执行不是基类组成部分的派生类操作的时候,需要动态的强制类型转换。通常,从基类指针获得派生类行为最好的方法是通过虚函数。当使用虚函数的时候,编译器自动根据对象的实际类型选择正确的函数。

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_caststd::cout << "------------1-------------\n";Base7 *pb7 = new Base7();// Child3* pc3 = dynamic_cast<Child3*>(&b7);   //OK,warning: 'dynamic_cast<class Child3*>(Base7 b7)' can never succeedChild3* pc3 = dynamic_cast<Child3*>(pb7);   //OK// pc3->doit();    //error,errorpc3->f();       //OKpc3 = nullptr;delete pb7;pb7 = nullptr;std::cout << "------------2-------------\n";}catch(const std::exception& e){std::cerr << e.what() << '\n';}catch(...){std::cout << "error\n";}
}

使用 dynamic_cast 操作符将基类类型对象的引用或指针转换为同一继承层次中其他类型的引用或指针。与 dynamic_cast 一起使用的指针必须是有效的,它必须为 0 或者指向一个对象。
        dynamic_cast 涉及运行时类型检查。如果绑定到引用或指针的对象不是目标类型的对象,则 dynamic_cast 失败。如果转换到指针类型的 dynamic_cast 失败,则 dynamic_cast 的结果是 0 值;如果转换到引用类型的 dynamic_cast 失败,则抛出一个 bad_cast 类型的异常。
        因此,dynamic_cast 操作符一次执行两个操作。它首先验证被请求的转换是否有效,只有转换有效,操作符才实际进行转换。一般而言,引用或指针所绑定的对象的类型在编译时是未知的,基类的指针可以赋值为指向派生类对象,同样,基类的引用也可以用派生类对象初始化,dynamic_cast 操作符执行的验证必须在运行时进行。
        但是,在某些情况下,不可能使用虚函数。在这些情况下,RTTI 提供了可选的机制。然而,这种机制比使用虚函数更容易出错:程序员必须知道应该将对象强制转换为哪种类型,并且必须检查转换是否成功执行了。使用动态强制类型转换要小心。只要有可能,定义和使用虚函数比直接接管类型管理好得多。

14.3 typeid 操作符与虚函数

typeid 操作符,typeid 操作符使程序能够问一个表达式:你是什么类型?用法是typeid(表达式)。如果表达式的类型是类类型且该类包含一个或多个虚函数,则表达式的动态类型可能不同于它的静态编译时类型。

例如,如果表达式对基类指针解引用,则该表达式的静态编译时类型是基类类型;但是,如果指针实际指向派生类对象,则 typeid 操作符将说表达式的类型是派生类型。
        typeid 操作符可以与任何类型的表达式一起使用。内置类型的表达式以及常量都可以用作 typeid 操作符的操作数。如果操作数不是类类型或者是没有虚函数的类,则 typeid 操作符指出操作数的静态类型;如果操作数是定义了至少一个虚函数的类类型,则在运行时计算类型。
        typeid 操作符的结果是名为 type_info 的标准库类型的对象引用,要使用 type_info 类,必须包含库头文件typeinfo。

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

只有当 typeid 的操作数是带虚函数的类类型的对象的时候,才返回动态类型信息。测试指针(相对于指针指向的对象)返回指针的静态的、编译时类型。

运行时类型识别(RTTI)为这类程序设计提供语言级支持。RTTI 只适用于定义了虚函数的类,没有定义虚函数的类型的类型信息是可用的但反映静态类型。

十五、完整源码

编译指令: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::fclass 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();    //errorpstr = (char*)malloc(size*sizeof(char));std::cout << "Base1\n";
}Base1::~Base1()
{f();    //OKdelete[] 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;//errorDerived_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_caststd::cout << "------------1-------------\n";Base7 *pb7 = new Base7();// Child3* pc3 = dynamic_cast<Child3*>(&b7);   //OK,warning: 'dynamic_cast<class Child3*>(Base7 b7)' can never succeedChild3* pc3 = dynamic_cast<Child3*>(pb7);   //OK// pc3->doit();    //error,errorpc3->f();       //OKpc3 = 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::doitc1.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();
};

c/c++开发,无可避免的自定义类类型(篇三).类与虚函数相关推荐

  1. 为什么基类的析构函数要声明成虚函数

    记得以后基类(父类) 的析构函数最好是声明为 虚函数 即:virtual 开发中遇到了一个比较傻逼的bug,也证明了理论与实际之间的差距. 在基类中没有声明其析构函数为虚函数,导致delete 释放操 ...

  2. 基类的析构函数不能被继承。_为什么要把C++类中的析构函数声明为虚函数?

    如题,当一个类为基类的时候,通常其析构函数被声明为虚函数,这是为啥? class BaseCls { public: BaseCls() { printf("BaseCls()n" ...

  3. 使用VS2010开发一个简单的自定义字段类型

    在SharePoint中创建自定义字段类型,必须完成两个工作:创建继承于SPField的字段类:编写字段的配置文件,声明字段的基本属性.字段的实现类和字段的呈现逻辑等. 首先在SharePoint站点 ...

  4. 静态联编,动态联编,类指针之间的关系,虚函数与多态性,纯虚函数,虚析构函数

    1.静态联编,是程序的匹配,连接在编译阶段实现,也称为早期匹配.重载函数使用静态联编. 2.动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编.switch语句和if语句是动态联编的例子. #i ...

  5. java 虚基类_重拾C++之虚函数和虚基类以及抽象类

    一.引言 好久没接触过C++了,今天突然要用一点感觉号蛋疼,用惯了python感觉C++一点都不会了. 声明了一个类的对象居然用这种方法,脑子绝对是被驴(python)踢了 class A{ ... ...

  6. 比较默认对象和默认约束的异同_UE4对象类类型引用和类默认对象(Class Default Object,简称CDO)...

    官方介绍https://docs.unrealengine.com/en-us/Programming/UnrealArchitecture/Objects The UCLASS macro give ...

  7. UE4对象类类型引用和类默认对象(Class Default Object,简称CDO)

    官方介绍https://docs.unrealengine.com/en-us/Programming/UnrealArchitecture/Objects The UCLASS macro give ...

  8. 形状类族的中的纯虚函数

    /* * 程序的版权和版本声明部分: * Copyright (c) 2013, 烟台大学计算机学院 * All rights reserved. * 文件名称:test.cpp * 作 者:任子仪 ...

  9. c++ 虚函数多态、纯虚函数、虚函数表指针、虚基类表指针详解

    文章目录 静态多态.动态多态 虚函数 哪些函数类型不可以被定义成虚函数? 虚函数的访问方式 析构函数中的虚函数 虚函数表指针 vptr 多继承下的虚函数表 虚基类表指针 bptr 纯虚函数 抽象类 虚 ...

最新文章

  1. 微调BERT:序列级和令牌级应用程序
  2. 公示 | 首届中国智能心电大赛初赛结果
  3. delphi编译缺少组件 imgedit excel2000等
  4. 缓存雪崩,缓存穿透,缓存预热,缓存热备都是什么鬼?
  5. uva 10985 Rings'n'Ropes
  6. 前端防抖和节流合二为一
  7. 使用squid代理后某些网站无法访问的解决办法(3.1.7版本)
  8. 计算机对操作系统函数的调用失败,win10调用DllRegisterServer失败怎么办_win10调用DllRegisterServer失败如何解决...
  9. 360修复上不了网络连接服务器失败,360断网急救箱网络连接配置修复不了怎么办-修复不了的解决办法...
  10. 影响力最大化算法——degreediscount以及python实现代码
  11. 关于笔记本自动睡眠更改设置无用的解决办法
  12. “铭赛科技”科创板IPO:“机器人王国梦”价值几何?
  13. 云服务优缺点_云服务是什么,它有哪些优缺点?
  14. 如何利用PDF转换器将WPS转换成word
  15. 【hadoop生态之Hive】Hive的DML数据操纵语言【笔记+代码】
  16. mac安装破解idea
  17. Chrome 开发者工具新功能-网络面板新增载荷(Payload)边栏
  18. GitHub上最火的两份Java面试小册,Star已经超百万
  19. Java培训,看这一篇就够了
  20. appium学习笔记

热门文章

  1. 西门子PLC各个通信协议解析,分析
  2. 关于Xilinx下Micro_Blaze中UartLite232外设的使用
  3. [笔记]软件框架设计的艺术
  4. 查询oracle 数据库版本
  5. [34期] 来兄弟连初感受
  6. c4c语言编译器,c4编译器源码剖析
  7. 薪资真相:程序员薪资被医生碾压!
  8. hls二次加密 m3u8_解决加密的HLS(m3u8)视频转换问题
  9. android dagger2官网,Dagger2在Android平台上的新魔法
  10. 1977年发生事件_1977年中國引發轟動的外星人劫持事件 _ 陽光UFO探秘