您的位置:首页 > 其它

关于重载、隐藏、重写的一些思考(2)

2017-11-05 16:27 344 查看
关于重载、隐藏、重写的一些思考(1)内容我们谈到了override,我说它和namehiding看上去很相似。我们先验证一下,它是否有namehiding的一些特征,并且又有哪些不同。

class Base
{
public:
virtual void func1() = 0;
virtual void func2();
virtual void func2(int);    //重载
void func3();
void func3(int);            //重载
};
class Derived: public Base
{
public:
virtual void func1();
virtual void func2();       //override
void func3();
void func4();
};


Derived d;
d.func1();      //调用Derived::func1()
d.func2();      //调用Derived::func2()重写了Base::func2() 隐藏了Base::func2(int)
d.func2(3);     //错误,无法调用Base::func2(int)
d.func3();      //调用Derived::func3()隐藏了Base::func3()和Base::func3(int)
d.func3(3);     //错误,无法调用Base::func3(int)


override和namehiding发生了同样的事情。好,很不错。

既然我们把一个东西藏起来了,那么我们也有办法把他找到。

class Base
{
public:
virtual void func1() = 0;
virtual void func2();
virtual void func2(int);    //重载
void func3();
void func3(int);            //重载
};
class Derived: public Base
{
public:
using Base::func3;          //新加入
virtual void func1();
virtual void func2();
void func3();
void func4();
};


d.func3();      //调用Base::func3()


嗯,我们似乎做的不错,我们也可以这样去调用
Base::func3()


d.Base::func3();    //调用Base::func3()


这些都恰恰说明了,隐藏仅仅是隐藏,它原来的东西还是有的,并没有消失。接着我们再看看发生override的
func2
,当我们这样去做的时候

d.Base::func2();     //调用Base::func2()<--------------
d.Base::func2(3);    //调用Base::func2(int)<-----------


ok,我们很愉快的看到了override和namehiding在这一点上又有了相似的特性。

我们再通过之前的一个小技巧,查看类的结构。

1>  class Base  size(4):
1>      +---
1>   0  | {vfptr}
1>      +---
1>
1>  Base::$vftable@:
1>      | &Base_meta
1>      |  0
1>   0  | &Base::func1
1>   1  | &Base::func2
1>   2  | &Base::func2
1>
1>  Base::func1 this adjustor: 0
1>  Base::func2 this adjustor: 0
1>  Base::func2 this adjustor: 0
1>
1>  class Derived   size(4):
1>      +---
1>   0  | +--- (base class Base)
1>   0  | | {vfptr}
1>      | +---
1>      +---
1>
1>  Derived::$vftable@:
1>      | &Derived_meta
1>      |  0
1>   0  | &Derived::func1
1>   1  | &Base::func2
1>   2  | &Derived::func2
1>
1>  Derived::func1 this adjustor: 0
1>  Derived::func2 this adjustor: 0


Derived虚函数表继承了Base虚函数表里面的所有函数,但是又对之前说的override的函数都进行自己的本地化,都变成了Derived自己的函数,这个过程是一种改写的过程。

很多奇怪的问题就不难理解了。诸如上面的

d.func2();      //调用Derived::func2()重写了Base::func2() 隐藏了Base::func2(int)


为什么会出现这种情况?

我觉得是这样的,首先我们建立了一个
Derived
类,建立之前会调用基类的构造函数,这个时候会在静态存储区(请看这篇虚函数表到底是怎么存放的)建立一个属于
Base
类的vtable,接着构造
Derived
类的时候又建立了一个属于
Derived
类的vtable(注意这些vtable都不是属于单一对象的(是公有的),但是vfptr是每个对象都独有的),copy了
Base
类的vtable里面的所有指向虚函数的指针,并且对
Base
类vtable里面的指向相同函数名相同参数列表函数的指针进行了本地化(改写)。如下图



最后通过汇编去看整个操作

Derived d;
01335C08  lea         ecx,[d]
01335C0B  call        Derived::Derived (013312E4h)
int x = 10;
01335C10  mov         dword ptr [x],0Ah
d.func1();      //调用Derived::func1()
01335C17  lea         ecx,[d]
01335C1A  call        Derived::func1 (013314C4h)
d.func2();      //调用Derived::func2()
01335C1F  lea         ecx,[d]
01335C22  call        Derived::func2 (013314C9h)
d.func3();      //调用Derived::func3()
01335C27  lea         ecx,[d]
01335C2A  call        Derived::func3 (013314E2h)
d.Base::func3();    //调用Base::func3()<--------------
01335C2F  lea         ecx,[d]
01335C32  call        Base::func3 (013314D8h)
d.Base::func2();    //调用Base::func2()<--------------
01335C37  lea         ecx,[d]
01335C3A  call        Base::func2 (013314D3h)
d.Base::func2(3);   //调用Base::func2(int)<-----------
01335C3F  push        3
01335C41  lea         ecx,[d]
01335C44  call        Base::func2 (013314CEh)


我们通过上述的列子可以看到,在静态绑定的情况下,namehiding和override表现出了一样的性质。

那到底哪不同呢?它们的不同出现在动态绑定时(我总结过什么时候会发生静态和动态绑定

Base *p = new Derived;
p->func2();             //调用Derived::func2()
p->func3();             //调用Base::func3()


对于namehiding来说,由于
Base::func3
不是virtual的,因此编译器会根据对象引用的静态类型而不是对象的实际类型来选择调用哪个函数。(this->func3)

对于override来说,由于
func2
是virtual的,因此编译器根据对象的实际类型类型来选择所需的函数。(this->vfptr->vtable->func2)

汇编层面看很清楚

p->func2();
010428FD  mov         eax,dword ptr [p]
01042900  mov         edx,dword ptr [eax]
01042902  mov         esi,esp
01042904  mov         ecx,dword ptr [p]
01042907  mov         eax,dword ptr [edx+8]
0104290A  call        eax
0104290C  cmp         esi,esp
0104290E  call        __RTC_CheckEsp (010411A9h)
p->func3();
01042913  mov         ecx,dword ptr [p]
01042916  call        Base::func3 (010414D8h)


所以大致总结如下:

在具有继承关系的不同类中,重写(override)仅发生在具有相同函数名且相同参数列表的virtual函数之间。(同函数名、同参数列表、virtual)

在具有继承关系的不同类(具有包含关系的不同作用域)中,具有相同函数名的非ivrtual函数之间(不论参数表是不是一样),都会发生隐藏。(同函数名、非virtual)

在具有继承关系的不同类中,具有相同函数名且参数列表不相同的virtual函数之间,都会发生隐藏。(同函数名、不同参数列表、virtual)

在相同类(作用域)中,函数名相同参数列表不同的函数之间是一种重载关系(overload)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: