关于重载、隐藏、重写的一些思考(2)
2017-11-05 16:27
344 查看
关于重载、隐藏、重写的一些思考(1)内容我们谈到了override,我说它和namehiding看上去很相似。我们先验证一下,它是否有namehiding的一些特征,并且又有哪些不同。
override和namehiding发生了同样的事情。好,很不错。
既然我们把一个东西藏起来了,那么我们也有办法把他找到。
嗯,我们似乎做的不错,我们也可以这样去调用
这些都恰恰说明了,隐藏仅仅是隐藏,它原来的东西还是有的,并没有消失。接着我们再看看发生override的
ok,我们很愉快的看到了override和namehiding在这一点上又有了相似的特性。
我们再通过之前的一个小技巧,查看类的结构。
Derived虚函数表继承了Base虚函数表里面的所有函数,但是又对之前说的override的函数都进行自己的本地化,都变成了Derived自己的函数,这个过程是一种改写的过程。
很多奇怪的问题就不难理解了。诸如上面的
为什么会出现这种情况?
我觉得是这样的,首先我们建立了一个
最后通过汇编去看整个操作
我们通过上述的列子可以看到,在静态绑定的情况下,namehiding和override表现出了一样的性质。
那到底哪不同呢?它们的不同出现在动态绑定时(我总结过什么时候会发生静态和动态绑定)
对于namehiding来说,由于
对于override来说,由于
汇编层面看很清楚
所以大致总结如下:
在具有继承关系的不同类中,重写(override)仅发生在具有相同函数名且相同参数列表的virtual函数之间。(同函数名、同参数列表、virtual)
在具有继承关系的不同类(具有包含关系的不同作用域)中,具有相同函数名的非ivrtual函数之间(不论参数表是不是一样),都会发生隐藏。(同函数名、非virtual)
在具有继承关系的不同类中,具有相同函数名且参数列表不相同的virtual函数之间,都会发生隐藏。(同函数名、不同参数列表、virtual)
在相同类(作用域)中,函数名相同参数列表不同的函数之间是一种重载关系(overload)
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)
相关文章推荐
- 关于重载、隐藏、重写的一些思考(1)
- 关于静态方法为什么不能被重写的一点思考以及overload的一些坑。
- C++菜鸟进阶——>关于重载、隐藏和重写
- 关于静态方法为什么不能被重写的一点思考以及overload的一些坑。
- 关于C++中的重载、重写(覆盖)、隐藏
- 关于静态方法为什么不能被重写的一点思考以及overload的一些坑
- 关于重载和重写的一些小知识
- 关于覆盖、隐藏、重载、重写的区别
- 关于性开放引发的一些思考
- C++_重载、重写(覆盖)和隐藏的区别:
- c++多态、重写、覆盖、隐藏、重载
- C++中重定义、重写、重载的区别以及隐藏与覆盖的访问
- 关于软件开发团队的一些思考
- C# 重载 重写 覆盖 隐藏
- 关于java传值还是传引用的一些思考
- 关于开发简易搜索引擎的一些总结和思考
- 关于vim究竟强大在哪里的一些思考和尝试
- C++中的重载、隐藏和重写
- 我的一些关于web标准的思考笔记(一)
- 关于软件开发团队的一些思考