cpp——类——virtual成员函数

VTBL

non-static成员函数分两类:
  • non-virtual成员函数
  • virtual成员函数
如果类包含virtual成员函数,就会生成虚函数表,即VTBL(virtual table),VTBL包含类的virtual成员函数入口地址:
  • 包含VTBL的类,其第一个字段为vptr指针,指向类的VTBL,因此VTBL被所有类实例对象共享
  • 子类VTBL继承父类VTBL,子类可增加VTBL项(增加virtual成员函数),更新VTBL项(override virtual成员函数),override virtual成员函数时,必须确保子类virtual成员函数原型与父类virtual成员函数原型完全一致(包括函数返回类型,函数名,函数形参列表)
  • 子类继承父类vptr,并初始化为指向子类VTBL
non-static成员函数调用:
  • non-virtual成员函数:通过类作用域名字查找规则找到相应non-virtual成员函数,因此编译期bind
  • virtual成员函数:编译器把virtual成员函数调用转换为通过vptr指针找到类VTBL,在类VTBL中找到相应virtual成员函数入口地址,通过该入口地址调用virtual成员函数,因此具体调用哪个类virtual成员函数依赖于vptr指向哪个类VTBL,因此运行期bind

non-virtual成员函数

subclass not override

class CAnimal
{
public:
    void feed()
    {
        cout << "CAnimal::feed()" << endl;
    }
    
    void work()
    {
        cout << "CAnimal::work()" << endl;
    }
};

class CDog : public CAnimal
{
};

void subclass_not_override()
{
    CAnimal animal;
    cout << "sizeof(animal) = " << sizeof(animal) << endl;
    animal.feed();
    animal.work();
    
    CDog dog;
    cout << "sizeof(dog) = " << sizeof(dog) << endl;
    dog.feed();
    dog.work();
}
output:
sizeof(animal) = 1
CAnimal::feed()
CAnimal::work()
sizeof(dog) = 1
CAnimal::feed()
CAnimal::work()

subclass override

class CAnimal
{
public:
    void feed()
    {
        cout << "CAnimal::feed()" << endl;
    }
    
    void work()
    {
        cout << "CAnimal::work()" << endl;
    }
};

class CDog : public CAnimal
{
public:
    void feed()
    {
        cout << "CDog::feed()" << endl;
    }
    
    void work()
    {
        cout << "CDog::work()" << endl;
    }
};

void subclass_override()
{
    CAnimal animal;
    cout << "sizeof(animal) = " << sizeof(animal) << endl;
    animal.feed();
    animal.work();
    
    CDog dog;
    cout << "sizeof(dog) = " << sizeof(dog) << endl;
    dog.feed();
    dog.work();
}
output:
sizeof(animal) = 1
CAnimal::feed()
CAnimal::work()
sizeof(dog) = 1
CDog::feed()
CDog::work()

总结

  • 空对象本不占用任何内存空间,其字长应为0,但对象有相应地址又必须占用内存空间,因此编译器分配了最小的内存长度1,而该内存空间中1字节的数据无意义,仅仅表示对象地址
  • non-virtual成员函数,编译期bind,因此是否被调用编译期即可确定,如果没有被调用,non-virtual成员函数可不实现,如果被调用,必须实现,否则link error

virtual成员函数

subclass not override

class CAnimal
{
public:
    virtual void feed()
    {
        cout << "CAnimal::feed()" << endl;
    }
    
    virtual void work()
    {
        cout << "CAnimal::work()" << endl;
    }
};

class CDog : public CAnimal
{
};

typedef void (*PVM)();

void subclass_not_override()
{
    CAnimal animal;
    cout << "sizeof(animal) = " << sizeof(animal) << endl;
    animal.feed();
    animal.work();
    cout << "animal vptr addr = " << (long*)&animal << endl;
    cout << "animal vbtl addr = " << (long*)*(long*)&animal << endl;
    cout << "animal feed addr = " << (long*)*((long*)*(long*)&animal) << endl;
    cout << "animal work addr = " << (long*)*((long*)*(long*)&animal + 1) << endl;
    PVM animal_vm1 = (PVM)(long*)*(long*)*(long*)&animal;
    PVM animal_vm2 = (PVM)(long*)*((long*)*(long*)&animal + 1);
    animal_vm1();
    animal_vm2();
    
    CDog dog;
    cout << "sizeof(dog) = " << sizeof(dog) << endl;
    dog.feed();
    dog.work();
    cout << "dog vptr addr = " << (long*)&dog << endl;
    cout << "dog vbtl addr = " << (long*)*(long*)&dog << endl;
    cout << "dog feed addr = " << (long*)*((long*)*(long*)&dog) << endl;
    cout << "dog work addr = " << (long*)*((long*)*(long*)&dog + 1) << endl;
    PVM dog_vm1 = (PVM)(long*)*(long*)*(long*)&dog;
    PVM dog_vm2 = (PVM)(long*)*((long*)*(long*)&dog + 1);
    dog_vm1();
    dog_vm2();
}
output:
sizeof(animal) = 8
CAnimal::feed()
CAnimal::work()
animal vptr addr = 0x7fff5fbff788
animal vbtl addr = 0x1000020e0
animal feed addr = 0x100001120
animal work addr = 0x100001170
CAnimal::feed()
CAnimal::work()
sizeof(dog) = 8
CAnimal::feed()
CAnimal::work()
dog vptr addr = 0x7fff5fbff770
dog vbtl addr = 0x100002110
dog feed addr = 0x100001120
dog work addr = 0x100001170
CAnimal::feed()
CAnimal::work()

subclass override

class CAnimal
{
public:
    virtual void feed()
    {
        cout << "CAnimal::feed()" << endl;
    }
    
    virtual void work()
    {
        cout << "CAnimal::work()" << endl;
    }
};

class CDog : public CAnimal
{
public:
    virtual void feed()
    {
        cout << "CDog::feed()" << endl;
    }
    
    virtual void work()
    {
        cout << "CDog::work()" << endl;
    }
};

typedef void (*PVM)();

void subclass_override()
{
    CAnimal animal;
    cout << "sizeof(animal) = " << sizeof(animal) << endl;
    animal.feed();
    animal.work();
    cout << "animal vptr addr = " << (long*)&animal << endl;
    cout << "animal vbtl addr = " << (long*)*(long*)&animal << endl;
    cout << "animal feed addr = " << (long*)*((long*)*(long*)&animal) << endl;
    cout << "animal work addr = " << (long*)*((long*)*(long*)&animal + 1) << endl;
    PVM animal_vm1 = (PVM)(long*)*(long*)*(long*)&animal;
    PVM animal_vm2 = (PVM)(long*)*((long*)*(long*)&animal + 1);
    animal_vm1();
    animal_vm2();
    
    CDog dog;
    cout << "sizeof(dog) = " << sizeof(dog) << endl;
    dog.feed();
    dog.work();
    cout << "dog vptr addr = " << (long*)&dog << endl;
    cout << "dog vbtl addr = " << (long*)*(long*)&dog << endl;
    cout << "dog feed addr = " << (long*)*((long*)*(long*)&dog) << endl;
    cout << "dog work addr = " << (long*)*((long*)*(long*)&dog + 1) << endl;
    PVM dog_vm1 = (PVM)(long*)*(long*)*(long*)&dog;
    PVM dog_vm2 = (PVM)(long*)*((long*)*(long*)&dog + 1);
    dog_vm1();
    dog_vm2();
}
output:
sizeof(animal) = 8
CAnimal::feed()
CAnimal::work()
animal vptr addr = 0x7fff5fbff788
animal vbtl addr = 0x1000020e0
animal feed addr = 0x100001070
animal work addr = 0x1000010c0
CAnimal::feed()
CAnimal::work()
sizeof(dog) = 8
CDog::feed()
CDog::work()
dog vptr addr = 0x7fff5fbff770
dog vbtl addr = 0x100002110
dog feed addr = 0x100001130
dog work addr = 0x100001180
CDog::feed()
CDog::work()

总结

  • CAnimal包含virtual成员函数,因此CAnimal拥有VTBL,类拥有vptr指针,指向VTBL,因此sizeof为8(不为1),CDog继承了CAnimal的VTBL,因此CDog拥有VTBL,CDog继承了CAnimal的vptr,指向CDog的VTBL,因此sizeof为8(不为1)
  • 没有override virtual成员函数时,CDog的VTBL和CAnimal的VTBL内容相同,override virtual成员函数时,CDog的VTBL和CAnimal的VTBL内容不同
  • virtual成员函数运行期bind,因此编译器无法在编译期确定virtual成员函数是否会被调用,为避免runtime crash,编译器要求所有virtual成员函数必须实现,否则link error(只要相应类实例化,virtual成员函数就可能被调用)

纯virtual成员函数

类引入virtual目的是实现virtual成员函数调用运行期bind,而非编译期bind,从而实现多态,当然会牺牲部分性能,因为virtual成员函数调用通过vptr找到VTBL,在VTBL中找到virtual成员函数入口地址,这个过程消耗了一些CPU指令
原则上virtual成员函数必须实现,因为virtual成员函数运行期bind,因此编译器无法在编译期确定virtual成员函数是否会被调用,为避免runtime crash,编译器要求所有virtual成员函数必须实现,但如果把virtual成员函数定义成纯virtual成员函数,就不能实现,纯粹作为接口使用,包含纯virtual成员函数的类称为抽象类,抽象类不可实例化,因此纯virtual成员函数的本质是告诉编译器当前类禁止实例化,不会被实例化也就确保了不会有vptr指向VTBL,从而确保该类的纯virtual成员函数不会被调用到,因此不实现是安全的
纯virtual成员函数作为特殊virtual成员函数(无实现)也会被子类继承,因此如果子类没有override实现所有纯virtual成员函数,子类依旧作为抽象类存在,也不能实例化
class CAnimal
{
public:
    virtual void feed() = 0;
    
    virtual void work() = 0;
};

class CDog : public CAnimal
{
public:
    virtual void feed()
    {
        cout << "CDog::feed()" << endl;
    }
    
    virtual void work()
    {
        cout << "CDog::work()" << endl;
    }
};

void pure_virtual_method()
{
    //CAnimal animal;
    cout << "sizeof(CAnimal) = " << sizeof(CAnimal) << endl;
    
    CDog dog;
    cout << "sizeof(dog) = " << sizeof(dog) << endl;
    dog.feed();
    dog.work();
}
output:
sizeof(CAnimal) = 8
sizeof(dog) = 8
CDog::feed()
CDog::work()
总结:
  • 包含纯virtual成员函数的类成为抽象类,抽象类仍然拥有VTBL,类实例对象也拥有vptr指针,抽象类与非抽象类的区别仅仅是编译器阻止了抽象类的实例化,目的为了避免runtime crash,抽象类子类也集成了抽象类的VTBL,因为子类如果没有override实现,依旧是抽象类,编译器也会阻止该子类的实例化
  • c++支持函数重载,函数名和形参列表唯一确定函数,返回类型和形参列表确定函数类型,因此子类如果override父类(pure)virtual成员函数,函数原型必须一致,这样编译器才能确定子类override父类哪个(pure)virtual成员函数,从而更新对应VTBL项
注:某些IDE中纯virtual成员函数optional实现,比如vs,如果实现了纯virtual成员函数,子类可调用(自身类无法调用,因为是抽象类,禁止实例化),某些IDE中纯virtual成员函数不可实现,比如xcode,只能纯粹作为接口使用,c++标准不允许纯virtual成员函数实现,为了代码移植,不建议纯virtual成员函数实现

多态

class CAnimal
{
public:
    virtual void feed()
    {
        cout << "CAnimal::feed()" << endl;
    }
};

class CDog : public CAnimal
{
public:
    virtual void feed()
    {
        cout << "CDog::feed()" << endl;
    }
};

void polymorphism()
{
    CAnimal animal;
    CDog dog;
    
    animal.feed();
    dog.feed();
    
    CAnimal* pAnimal;
    
    pAnimal = &animal;
    pAnimal->feed();
    
    pAnimal = &dog;
    pAnimal->feed();
}
output:
CAnimal::feed()
CDog::feed()
CAnimal::feed()
CDog::feed()
总结:
  • animal.feed()等价于feed(&animal),dog.feed()等价于feed(&dog),this的vptr指针分别指向CAnimal的VTBL和CDog的VTBL
  • pAnimal->feed()等价于feed(pAnimal),this的vptr指针指向哪个VTBL依赖于pAnimal指向哪个类实例对象
  • 子类指针可隐式转换为父类指针是实现多态的语法基础

特殊函数的virtual

构造函数

构造函数不允许定义为virtual:
  • 构造函数调用前,实例对象数据成员没有初始化,包括vptr,因此寻址不到VTBL,从实现上来说不可能
  • 对象实例化时,编译器已明确要构造什么类实例对象,因此编译期即可确定调用哪个类构造函数,因此即使语法上允许构造函数virtual,事实上也无必要

析构函数

析构函数允许定义为virtual:
  • 虽然子类和父类析构函数函数名不一致,但析构函数函数名是确定的(~类名)而且是唯一的(无参无重载),因此编译器能识别析构函数
non-virtual析构函数:
class CAnimal
{
public:
    ~CAnimal()
    {
        cout << "~CAnimal()" << endl;
    }
};

class CDog : public CAnimal
{
public:
    ~CDog()
    {
        cout << "~CDog()" << endl;
    }
};

void non_virtual_destruct()
{
    CAnimal* pAnimal = new CDog();
    delete pAnimal;
    
    CDog* pDog = new CDog();
    delete pDog;
}
output:
~CAnimal()
~CDog()
~CAnimal()
分析:
  • delete pAnimal等价于析构函数(pAnimal),delete pDog等价于析构函数(pDog),析构函数non-virtual,因此通过类作用域名字查找规则找到相应析构函数调用
注:在目标代码中,析构函数函数名被编译为特定的唯一名(所有析构函数在目标代码中函数名都一样),并非跟特定类名相关
virtual析构函数:
class CAnimal
{
public:
    virtual ~CAnimal()
    {
        cout << "~CAnimal()" << endl;
    }
};

class CDog : public CAnimal
{
public:
    virtual ~CDog()
    {
        cout << "~CDog()" << endl;
    }
};

void virtual_destruct()
{
    CAnimal* pAnimal = new CDog();
    delete pAnimal;
    
    CDog* pDog = new CDog();
    delete pDog;
}
output:
~CDog()
~CAnimal()
~CDog()
~CAnimal()
分析:
  • delete pAnimal等价于析构函数(pAnimal),delete pDog等价于析构函数(pDog),析构函数virtual,因此this的vptr指针分别指向CAnimal的VTBL和CDog的VTBL,从而实现多态
注:在类继承体系中,为防止内存泄露,父类析构函数应定义为virtual
合成析构函数:
class CAnimal
{
public:
    virtual ~CAnimal()
    {
        cout << "CAnimal::~CAnimal()" << endl;
    }
};

class CDog : public CAnimal
{
};

void default_destruct()
{
    CAnimal animal;
    cout << "sizeof(animal) = " << sizeof(animal) << endl;
    cout << "animal vptr addr = " << (long*)&animal << endl;
    cout << "animal vbtl addr = " << (long*)*(long*)&animal << endl;
    cout << "animal ~CAnimal addr = " << (long*)*((long*)*(long*)&animal) << endl;
    
    CDog dog;
    cout << "sizeof(dog) = " << sizeof(dog) << endl;
    cout << "dog vptr addr = " << (long*)&dog << endl;
    cout << "dog vbtl addr = " << (long*)*(long*)&dog << endl;
    cout << "dog ~CDog addr = " << (long*)*((long*)*(long*)&dog) << endl;
}
output:
sizeof(animal) = 8
animal vptr addr = 0x7fff5fbff7a8
animal vbtl addr = 0x1000020f0
animal ~CAnimal addr = 0x1000010a0
sizeof(dog) = 8
dog vptr addr = 0x7fff5fbff790
dog vbtl addr = 0x100002120
dog ~CDog addr = 0x100001080
CAnimal::~CAnimal()
CAnimal::~CAnimal()
分析:
  • 子类(CDog)析构函数和父类(CAnimal)析构函数addr不同,说明了子类(CDog)确实有析构函数合成,并且调用了父类(CAnimal)析构函数

赋值操作符函数

赋值操作符函数允许定义为virtual:
  • 赋值操作符函数只是特殊普通成员函数,赋值操作符重载的特殊版本,形参列表为[const] classname&,自然可定义为virtual
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值