将基类指针强制转换成派生类指针有什么问题

kingstarer 2009-12-27 12:51:26
以前看过好几篇文章都说不允许将基类指针强制转换成派生类指针
因为当时没这个需求,所以就只记住了原则,没记住原理

但是现在自己碰上了这个需求:
从网上下载了一个解析xml的类库,里面提供的成员函数用起来不方便
于是我写了一个新类继承自原类,在里面提供了几个新的成员函数
将一些常见的操作放到里面,这样写起程序来比较方便
只要把基类指针强制转成派生类就可以调用这些函数了

对于这个,我想了一下
首先,派生类只增加了成员函数,成员函数又不在对象里面
所以内存结构不变,强制转换后只是类型不同,this指针的值也没变。

其次,成员函数的调用,据我理解,其实跟普通函数调用相似
只是由编译器负责传this指针,而根据上面可知,this指针值没变,
指向的内存也没变,所以派生类的成员函数应该能正常调用的

示例代码:

#include "stdafx.h"
class BaseClass
{
public:
int getPassed() { return m_passed; }
BaseClass (int passsed):m_passed(passsed) {}
protected:
int m_passed;
};

class ChildBaseClass: public BaseClass
{
public:
ChildBaseClass():BaseClass(0) {}
void setPassed(int passed) {m_passed = passed;}
};

int main(int argc, char *argv[])
{
ChildBaseClass child;
cout << child.getPassed() << endl;
child.setPassed(1);
cout << child.getPassed() << endl;

BaseClass base(0);
cout << base.getPassed() << endl;
ChildBaseClass *pchild = (ChildBaseClass *) &base;
pchild->setPassed(1);
cout << base.getPassed() << endl;
return 0;
}


========================
不知道我上面的理由有什么漏洞? 或者是不允许转成派生类的原则并不是指的这种情况?
...全文
1359 18 打赏 收藏 转发到动态 举报
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
marskahn 2011-12-19
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 dskit 的回复:]

如果你确保基类指针实际上指向的是你将要转换的派生类类型,也不是不可以转换。
如果安全的实现这样的转换呢, 使用RTTI, 运行时类型识别, 顺便说一句,据说RTTI的花销很大(见深度探索C++对象模型)
[/Quote]

最近读代码也遇到了这样的疑惑,和楼主的问题非常相像。实际上应该是可以的,只不要小心。
从别处看到的一句话觉得很关键“类型强制转化后,指向的地址相同. 但会按转化类型访问数据成员.”
卡卡先生 2011-09-17
  • 打赏
  • 举报
回复
ChildBaseClass *pchild = (ChildBaseClass *) &base
这样是错误的吧,《c++primer》中曾提到即使是基类的指针或者引用实际绑定的是派生类对象,比如
drive *pt1,app;
base *pt2=&app;
pt1=(drive *)pt2,//这样转换都可能出现错误,何况楼主的转换强行转换,肯定会出现错误的
dahaiI0 2011-08-26
  • 打赏
  • 举报
回复
马克···
arong1234 2009-12-27
  • 打赏
  • 举报
回复
“不允许”这种说法是不正确的,如果一个派生类对象被一个基类指针指着,当然可以强制转换。

重要的是,你必须确保这种转换是合法的。例如一个基类有两个不同的派生类A,B,一个指针指向B对象,你强制转换为A*,当然是不允许的

你把系统库提供的接口中返回的“基类”指针强制转换为自己的派生类,实际是不合适的。因为这个指针实际和你的派生类没有关系。给现有的基类提供新功能,不是你自己随便派生一个类就可以了,然后直接把指针强制转换过去就可以了。这种新增功能需要修改的东西是很多的,例如:必须强迫你那个库返回的指针指向的是你新实现的这个类的对象,而这显然是不可能的
dskit 2009-12-27
  • 打赏
  • 举报
回复
如果你确保基类指针实际上指向的是你将要转换的派生类类型,也不是不可以转换。
如果安全的实现这样的转换呢, 使用RTTI, 运行时类型识别, 顺便说一句,据说RTTI的花销很大(见深度探索C++对象模型)
kingstarer 2009-12-27
  • 打赏
  • 举报
回复
[Quote=引用 11 楼 arong1234 的回复:]
所谓的wrapper, has-a,就是把库返回的指针,当作另外一个类的成员变量,然后那个类可以提供丰富的接口,去处理这个指针
[/Quote]
这个主意听起来不错


#include "stdafx.h"

class BaseClass
{
public:
int getPassed() { return m_passed; }
BaseClass (int passsed):m_passed(passsed) {}
protected:
int m_passed;
};

class WrapperBaseClass
{
public:
WrapperBaseClass(BaseClass *pBase):m_pBase(pBase) {assert(pBase);}

void setPassed(int passed)
{
BaseClass *pBase = new BaseClass(passed);
*m_pBase = *pBase;
delete pBase;
}

BaseClass& getBase(){ return *m_pBase; }
BaseClass* get(){ return m_pBase; }

private:
WrapperBaseClass(const WrapperBaseClass& other) {/* 不允许复制 */}

protected:
BaseClass *m_pBase;
};

int main(int argc, char *argv[])
{
BaseClass base(0);
cout << base.getPassed() << endl;
WrapperBaseClass wrapper(&base);
wrapper.setPassed(1);
cout << wrapper.getBase().getPassed() << endl;
cout << base.getPassed() << endl;

return 0;
}

traceless 2009-12-27
  • 打赏
  • 举报
回复
你可以使用10L 和 11L 的设计方法
kingstarer 2009-12-27
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 wanggang999 的回复:]
引用 6 楼 arong1234 的回复:
至于“确保尺寸相同”,第一这很难做到(你很难根据一个指针判断他指向的对象到底多大),第二,如果你有办法判断大小,那么有一天有人加了个成员变量,发现整个系统突然停顿不干活了,他怎么可能知道这是因为加了个变量导致的?为了你这种技术,可能花费一个人几个星期的努力来找到答案,这当然是不可接收的
引用 4 楼 wanggang999 的回复:
我觉得你这个情况,应该是可以转的,基类指针转换成派生类指针,在不是多继承的情况下,
会导致问题的因素应该就是二者在内存中保存的内容不同,派生类访问基类中不存在的变量就有问题了,
但你仅仅添加了成员函数,应该不会有上面的问题。

但是我有个建议,就是写一个方法,把你这个转换封装起来,在里面assert一下二者所占内存大小相同,
然后写一个详细的注释在里面,因为这代码将来由别人维护修改的话,看到你强转肯定是有疑问的,
封装了以后,方便将来管理,比方说有一天你发现一定要添加新的成员变量了,就可以不用强制转换,
而采用 dynamic_cast 之类的方法, over 。



嗯,确实,刚才看代码看得不够细,就如 arong1234 所说的,还是不要做这么危险的事情了,
就算现在可以满足你这些假设,将来一定会成为严重的限制,会导致你不得不为了违背这个假设的,
为什么不在你的项目里直接用你的派生类呢?在需要基类的时候,派生类照样管事,
你也可以很自由的修改这个派生类了,不是很好么?
[/Quote]
我用的是thnyxml。里面提供了一个文档类TiXmlDocument,在文档类构造时会根据xml
的结构生成TiXmlElement对象,表示一个xml结点,并放在链表中
我写的派生类TiXmlElementForText就是为了方便操作TiXmlElement对象的
由于不想去改TiXmlDocument的代码,所以没办法在整个项目中统一使用派生类指针
arong1234 2009-12-27
  • 打赏
  • 举报
回复
所谓的wrapper, has-a,就是把库返回的指针,当作另外一个类的成员变量,然后那个类可以提供丰富的接口,去处理这个指针
traceless 2009-12-27
  • 打赏
  • 举报
回复
has-a是私有继承,使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。

has-a可以实现的,is-a都可以实现。

如果觉得直接操作子类的指针,显得不那么具有对象化和封装性,如我上面说的,可以增加
接口。
traceless 2009-12-27
  • 打赏
  • 举报
回复
如果一定要用公用继承,再说公用继承(is-a)能让代码维护起来也方便

LZ你可以考虑另种设计思路呀:

可以增加一个接口,里面放的是纯虚函数,是你自己改进后需要外调的函数。
然后新类继承自原类和接口。

采用接口的方式用得方便,不管是做成dll供应用层使用还是底层内部模块间
相互调用。
windsting 2009-12-27
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 arong1234 的回复:]
至于“确保尺寸相同”,第一这很难做到(你很难根据一个指针判断他指向的对象到底多大),第二,如果你有办法判断大小,那么有一天有人加了个成员变量,发现整个系统突然停顿不干活了,他怎么可能知道这是因为加了个变量导致的?为了你这种技术,可能花费一个人几个星期的努力来找到答案,这当然是不可接收的
引用 4 楼 wanggang999 的回复:
我觉得你这个情况,应该是可以转的,基类指针转换成派生类指针,在不是多继承的情况下,
会导致问题的因素应该就是二者在内存中保存的内容不同,派生类访问基类中不存在的变量就有问题了,
但你仅仅添加了成员函数,应该不会有上面的问题。

但是我有个建议,就是写一个方法,把你这个转换封装起来,在里面assert一下二者所占内存大小相同,
然后写一个详细的注释在里面,因为这代码将来由别人维护修改的话,看到你强转肯定是有疑问的,
封装了以后,方便将来管理,比方说有一天你发现一定要添加新的成员变量了,就可以不用强制转换,
而采用 dynamic_cast 之类的方法, over 。

[/Quote]

嗯,确实,刚才看代码看得不够细,就如 arong1234 所说的,还是不要做这么危险的事情了,
就算现在可以满足你这些假设,将来一定会成为严重的限制,会导致你不得不为了违背这个假设的,
为什么不在你的项目里直接用你的派生类呢?在需要基类的时候,派生类照样管事,
你也可以很自由的修改这个派生类了,不是很好么?
kingstarer 2009-12-27
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 arong1234 的回复:]
如果你能确保下面这些,其实转换没那么危险。危险在于,你往往无法保证。如果只是自己写的三两个文件的小程序,当然没啥问题,你知道所有的细节,不会违背这几条。但是当你的项目被几十个人同时维护,很多人并不知道你的假设的时候,这就非常危险了。大家看到的只是你提供了一个有更强功能的类的指针,不知道这个类必须保证这几条,有个人也许想给你的类增强一下,加了两个成员变量和3个成员函数,这是你的代码就不知道会发生什么事情了。

因此不要做这种危险的事情,你的这些假定都只是你自己脑海里保证的约束,编译器和语言都不会知道你这种约束。如果你想给现有的类提供额外功能,你需要使用的是wrapper,也就是大家常说的has-a技术,而不是派生(也就是is-a技术)
引用楼主 kingstarer 的回复:
对于这个,我想了一下
首先,派生类只增加了成员函数,成员函数又不在对象里面
所以内存结构不变,强制转换后只是类型不同,this指针的值也没变。

其次,成员函数的调用,据我理解,其实跟普通函数调用相似
只是由编译器负责传this指针,而根据上面可知,this指针值没变,
指向的内存也没变,所以派生类的成员函数应该能正常调用的
[/Quote]

谢谢,我确实没考虑到这个问题,要是以后真有人加了成员变量确实可能会出现问题

关于wrapper 这个具体是什么呢? 百度搜索出来的资料很少

has-a 是定义一个新类,把旧类对象做为一个成员,并且实现旧类对象的所有接口(return 成员对象.接口)吗?
arong1234 2009-12-27
  • 打赏
  • 举报
回复
至于“确保尺寸相同”,第一这很难做到(你很难根据一个指针判断他指向的对象到底多大),第二,如果你有办法判断大小,那么有一天有人加了个成员变量,发现整个系统突然停顿不干活了,他怎么可能知道这是因为加了个变量导致的?为了你这种技术,可能花费一个人几个星期的努力来找到答案,这当然是不可接收的
[Quote=引用 4 楼 wanggang999 的回复:]
我觉得你这个情况,应该是可以转的,基类指针转换成派生类指针,在不是多继承的情况下,
会导致问题的因素应该就是二者在内存中保存的内容不同,派生类访问基类中不存在的变量就有问题了,
但你仅仅添加了成员函数,应该不会有上面的问题。

但是我有个建议,就是写一个方法,把你这个转换封装起来,在里面assert一下二者所占内存大小相同,
然后写一个详细的注释在里面,因为这代码将来由别人维护修改的话,看到你强转肯定是有疑问的,
封装了以后,方便将来管理,比方说有一天你发现一定要添加新的成员变量了,就可以不用强制转换,
而采用 dynamic_cast 之类的方法, over 。
[/Quote]
arong1234 2009-12-27
  • 打赏
  • 举报
回复
他这种情况dynamic_cast必然失败。dynamic_cast要求基类指针指向的真是派生类指针,而他的显然不满足这一点
[Quote=引用 4 楼 wanggang999 的回复:]
我觉得你这个情况,应该是可以转的,基类指针转换成派生类指针,在不是多继承的情况下,
会导致问题的因素应该就是二者在内存中保存的内容不同,派生类访问基类中不存在的变量就有问题了,
但你仅仅添加了成员函数,应该不会有上面的问题。

但是我有个建议,就是写一个方法,把你这个转换封装起来,在里面assert一下二者所占内存大小相同,
然后写一个详细的注释在里面,因为这代码将来由别人维护修改的话,看到你强转肯定是有疑问的,
封装了以后,方便将来管理,比方说有一天你发现一定要添加新的成员变量了,就可以不用强制转换,
而采用 dynamic_cast 之类的方法, over 。
[/Quote]
windsting 2009-12-27
  • 打赏
  • 举报
回复
我觉得你这个情况,应该是可以转的,基类指针转换成派生类指针,在不是多继承的情况下,
会导致问题的因素应该就是二者在内存中保存的内容不同,派生类访问基类中不存在的变量就有问题了,
但你仅仅添加了成员函数,应该不会有上面的问题。

但是我有个建议,就是写一个方法,把你这个转换封装起来,在里面assert一下二者所占内存大小相同,
然后写一个详细的注释在里面,因为这代码将来由别人维护修改的话,看到你强转肯定是有疑问的,
封装了以后,方便将来管理,比方说有一天你发现一定要添加新的成员变量了,就可以不用强制转换,
而采用 dynamic_cast 之类的方法, over 。
arong1234 2009-12-27
  • 打赏
  • 举报
回复
考虑问题千万不要用3行测试代码来证明,这往往是毫无意义的
arong1234 2009-12-27
  • 打赏
  • 举报
回复
如果你能确保下面这些,其实转换没那么危险。危险在于,你往往无法保证。如果只是自己写的三两个文件的小程序,当然没啥问题,你知道所有的细节,不会违背这几条。但是当你的项目被几十个人同时维护,很多人并不知道你的假设的时候,这就非常危险了。大家看到的只是你提供了一个有更强功能的类的指针,不知道这个类必须保证这几条,有个人也许想给你的类增强一下,加了两个成员变量和3个成员函数,这是你的代码就不知道会发生什么事情了。

因此不要做这种危险的事情,你的这些假定都只是你自己脑海里保证的约束,编译器和语言都不会知道你这种约束。如果你想给现有的类提供额外功能,你需要使用的是wrapper,也就是大家常说的has-a技术,而不是派生(也就是is-a技术)
[Quote=引用楼主 kingstarer 的回复:]
对于这个,我想了一下
首先,派生类只增加了成员函数,成员函数又不在对象里面
所以内存结构不变,强制转换后只是类型不同,this指针的值也没变。

其次,成员函数的调用,据我理解,其实跟普通函数调用相似
只是由编译器负责传this指针,而根据上面可知,this指针值没变,
指向的内存也没变,所以派生类的成员函数应该能正常调用的 [/Quote]

65,179

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧