C++/C#的区别之在构造函数中调用虚函数
来源:互联网 发布:韩国高清网络电视直播 编辑:程序博客网 时间:2024/05/22 00:16
今天在Essential C# 2.0里面看到的一段话:
In C++, methods called during construction will not dispatch the virtual method. Instead, during construction, the type is associated with the base type rather than the derived type, and virtual methods call the base implementation. In contrast, C# dispatches virtual method calls to the most derived type. This is consistent with the principal of calling the most derived virtual member, even if the derived constructor has not completely executed.
引发的下面的故事:
结论:
1. 上面的那段话是正确的(好像这个结论比较废话)
2. C++中的指针,永远只是一个指针,一个32bit的值,它不随他指向的类型变化而变化。
故事过程:
为了验证上面的两个结论,我用C++和C# 分别写了3个类,继承关系:
验证1:
C++
C#
C++ 结果
C#结果
分析:
无争议过程:先调基类构造函数,在执行本类构造函数内的函数。
C++/C#区别:
C++在调用构造函数的时候,如果调用了虚函数,那么,这个调用过程,不会去寻找继承类的该函数重写。(有点绕口:看结果截图:Root构造时,只调用它自己的TestFunc,不会去寻找继承类的TestFunc)
C#在调用构造函数的时候,如果调用了许函数,那么,基类回去检查继承类是否重写了这个函数,如果有,就调用。(截图里可以看出, Root构造的时候,调L2的TestFunc, L1构造的时候,也调用L2的TestFunc)
这里是从表现上证明了文章开始的那段话。
但是为什么呢?我根据我的理解如下,在基类的构造函数中调用虚函数 A:
C++ 不会直接调用派生类的重写方法A; C# 会查找派生类是否重写了方法A,如果重写了,那么调用派生类的方法A。这里就出现了一个比较“Tricky”的问题。
这里我设置了一个比较特殊的虚函数A, 这个虚函数A, 对基类中的一些数据成员m_val做了修改。
C++说:我这个基类的数据成员必须在这个虚函数A里面进行操作!因此,我的构造函数在调用这个虚函数的时候,必须调用自己的A函数体,而不是继承类的A函数体。
C#说:我的这个基类的成员调用了虚函数,从多态的角度上看,我必须要检查我的派生类是否重写了这个函数。如果重写了,必须要调用派生类的这个函数!!因此,我调用了派生类的A函数。
所以,如果在C#中,m_val是一个很关键的数据,没有被正常赋值而造成了崩溃。但是C++似乎有违背了多态的特性...所以,为了避免这样的问题出现,我觉得一个比较稳妥的办法是,别在构造函数里面调用虚函数。如果因为代码太长,需要用函数的话,也不要把这个函数定义成虚函数...后患无穷阿...
验证2:
C++中,类指针就是一个指针,不是别的!!
还是上面例子中的3个类,但是main函数有点儿区别:
这两次调用的唯一区别是 用实例名调用和类指针调用。
而出来的结果却是截然不同的:
下面做一下分析:
实例对象调用函数:
1。因为12c/rc都是类实例,因此分别调用了构造函数。
2。r2 = l2c。 这里就是出现区别的地方。咱们看一下汇编码:
在r2 =l2c的时候,调用了r2 的默认类赋值操作符函数
RootClass::operator= (1B12A3h)
事实上,
1)。如果r2 和l2c不是继承关系,编译器会在编译时报错。
2)。在我们没有重写默认类赋值操作符函数的时候,赋值操作符不会做任何操作。这一点,内存可以证明:
(赋值前后的内存变化,左边是rc的指针,右边是l2c的指针)
3。如果修改一下代码: rc = (RootClass)l2c,他就会调用默认拷贝构造函数....然后调用了默认赋值操作符重写:
00CB1B08 lea eax,[ebp-14h]
00CB1B0B push eax
00CB1B0C lea ecx,[ebp-128h]
00CB1B12 call RootClass::RootClass (0CB12A8h)
00CB1B17 push eax
00CB1B18 lea ecx,[ebp-20h]
00CB1B1B call RootClass::operator= (0CB12A3h)
4。因为上面一步的赋值操作没有任何变化,所以,调用TestFunc的时候,就会直接调用RootClass的函数。输出自然就像截图所示。
类指针调用函数:
1。调用了L2类的构造函数,因为new了
2。因为在程序中,只是声明RootClass的类指针,所以不调用任何构造函数。
3 。然后再看看赋值操作:因为他们都是指针,所以直接赋值!!
65: pRc = pl2c;
00CB1BB7 mov eax,dword ptr [ebp-2Ch]
00CB1BBA mov dword ptr [ebp-38h],eax
(左边是pRc的指针,右边是pl2c)
4。因此在pRc->TestFunc()的时候,就是用L2的类指针调用了实例方法。
结论,指针赋值就是指针指的赋值,类型检查交给编译器。类赋值是需要深度拷贝才能实现的类复制的。多态的实现是靠指针实现的...
恩。写完收工......
虽然都是很基础的东西,但是还是需要好好理解阿...
有板砖的拍过来啊...
(注:以上均为32位机器程序结果)
- C++/C#的区别之在构造函数中调用虚函数
- C++:构造函数中调用虚函数
- C++:构造函数中调用虚函数
- Java与C++在构造函数中调用虚函数时的区别
- 在构造函数中调用虚函数
- 在构造函数中调用虚函数
- 在子类的构造函数中调用虚函数
- c++:在类的构造函数中调用另一个构造函数
- 在构造函数中调用构造函数
- 在构造函数中调用构造函数
- 《Effective C++》不要在构造函数和析构函数中调用虚函数
- 在C#中调用C语言函数
- 在构造函数/析构函数中调用虚函数
- 在构造函数/析构函数中调用虚函数
- 在C#中调用C++Dll函数的方法
- C#静态构造函数,在继承中调用情况
- 在C#的构造函数中,如何显式的先调用父类或者是自己的构造函数?
- 在成员函数中调用构造函数
- android (23)
- 2011-7-25 9:00:12
- socket编程原理
- iphone/ipad保存图片问题
- 使static控件背景透明(MFC)
- C++/C#的区别之在构造函数中调用虚函数
- eclipse--tomcat内存配置 tomcat添加到系统服务 及其配置 解压版tomcat 6.0 多个tomcat
- _kCATransitionFade", referenced from:错误原因
- 数组和对象
- 无法访问。您可能没有权限使用网络资源。……拒绝访问
- How to simulate a Form POST request by using WinInet
- find搜索目录下所有文件内容中的某个单词
- 关于OnNcLButtonUp不能响应消息的问题
- JNDI 是什么