foxriver
Keep It Simple and Stupid.
posts - 12,  comments - 39,  trackbacks - 0
算是对多重继承里,类型转换所做一个笔记。先看如下代码:

class A
{
public:
     A() { m_data 
= 'A';}
    
~A() {}
    
char m_data;
};

class B
{
public:
     B() { m_data 
= 'B';}
    
~B() {}
    
char m_data;
};

class C : public A, public B
{
public:
     C() { m_data 
= 'C';}
    
~C() {}
    
char m_data;
};

class D : public C
{
public:
     D() { m_data 
= 'D';}
    
~D() {}

    
void test()
    {
        DWORD value 
= (DWORD)this;

        A
* address1 = (A*)(value);// 编译通过,类型转换错误(仅在在虚拟继承的情况下),正确的写法:A* address1 = (A*)((D*)value);
        B* address2 = (B*)(value);// 编译通过,类型转换错误,正确的写法:B* address2 = (B*)((D*)value);
        C* address3 = (C*)(value);
        D* address4 = (D*)(value);

        printf(
"%c %c %c %c", address1->m_data, address2->m_data, address3->m_data, address4->m_data);
    }

    
char m_data;
};

void main()
{
  D d;
  d.test();
}

代码运行后,结果为A A C D,显然B这个类没有正确转换。

A和B都是D的父类,为什么A* address1 = (A*)value这句转换正确,而B* address2 = (B*)(value)出错呢?这就是多重继承的不可判断性。

正因为这种特性的存在,我们在实际使用中,应该尽量避免多重继承,选择单一继承这种模式。JAVA就是如此,最初设计时就只能单一继承,而多重继承则演变为纯虚接口(interface),这样就规避了此类问题。但可惜,在C++里,WTL和QT都大量使用这种模型,想在实际项目中完全避免,也很困难。

要解决,有几种方法。

1. 把B* address2 = (B*)(value)这行,改写为B* address2 = (B*)((D*)value); 这样就能直观的传达给编译器,B正确的偏移量。

最终输出A B C D,正是我们想要的结果。

2. 显示使用static_cast,当编译器不能确定转换类型时,会提示编译错误信息。

例如:
B* address2 = static_cast<B*>(value); // 编译失败。
B* adddres2 = static_cast<B*>((D*)value); // 编译成功,并且结果正确。

3. 使用RTTI解决。


--------------------------------------------------

看似问题解决了,可如果一旦改写为虚拟继承(class C : virtual  public A, virtual public B)这种形式,A运行时还是会出错,必须写成A* address1 = (A*)((D*)value);。如程序里用到了多重继承,一定要小心+谨慎。

posted on 2011-01-12 15:53 foxriver 阅读(6329) 评论(10)  编辑 收藏 引用

FeedBack:
# re: C++ 多重继承的强制类型转换。
2011-01-12 20:26 | 空明流转
这个和多重继承也没关系。  回复  更多评论
  
# re: C++ 多重继承的强制类型转换。
2011-01-12 22:23 | foxriver
@空明流转

class C : public A, public B
类C同时继承了A和B,这就是多重继承。 这篇文章的本意是虽然B是D的子类,但是不能直接用(B*)做类型转换,需要写成(B*(D*))这种形式,因为B和D的关系不是单根继承。

ps:虚函数的那种继承叫虚拟继承。

  回复  更多评论
  
# re: C++ 多重继承的强制类型转换。
2011-01-13 01:29 | oldman
是多重继承的原因。
class C : public A, public B
假设有一个变量 C*c, 那么 (A*)c和(B*)c在数值上即时不同的(因为编译器在处理(B*)c的时候,会帮你矫正B的偏移量)
文中将对象指针转成long,丢失了C和B的关系,再将long转成B*,就只是简单的将类型转化下了,即
(A*)c != (A*)(void*)c

不过文中提到的用static_cast转换,这个应该是不对的。使用static_cast转换也不能让static_cast<B*>((void*)c)指向正确的位置  回复  更多评论
  
# re: C++ 多重继承的强制类型转换。
2011-01-13 01:33 | oldman
@foxriver
虚继承是指 这样的
class A{ int data; };
class B: virtual public A{} //注意这里
class C: virtual public A{} //注意这里
class D: public B, public C{}

虚继承主要是处理基类成员二义性的  回复  更多评论
  
# re: C++ 多重继承的强制类型转换。
2011-01-13 10:06 | jmchxy
根本就是类型转换使用错误。

A* address1 = (A*)(value);

这个转换, address1 直接就等于 value 的值(因Value不是A的子类型)。  回复  更多评论
  
# re: C++ 多重继承的强制类型转换。
2011-01-13 11:13 | foxriver
@jmchxy

你说对了一半,address1确实等于value的值(也就是D的内存值)。但在单一继承的情况,这种转换的写法是绝对允许的,而且结果是完全正确。

唯独多重继承情况下会出错,我就把这种情况归结为多重继承的问题了。

单一继承下,A,B,C,D共用一个D的内存地址是没问题的。
多重继承下,A,B,C,D共用一个D的内存地址是不可以的。

这就是两者最大的区别,也是文章核心所指 --- 父类是否能和子类共享同一内存地址。

  回复  更多评论
  
# re: C++ 多重继承的强制类型转换。
2011-01-13 22:22 | oldman
@foxriver

呵呵,好像内容改过了。因为我印象中昨天看到的文章不是这个。

有个问题指出下:
2. 显示使用static_cast,当编译器不能确定转换类型时,会提示编译错误信息。

这个地方是不正确的,B* address2 = static_cast<B*>(value);会编译出错的原因是由于“编译器禁止由整数数值static_cast到指针数值”(并不是不能确定转换类型),因为这种转化是static_cast能力之外的,如果想做这种转化,须使用reinterpret_cast,即 B* address2 = reinterpret_cast<B*>(value); 这样编译才会通过(当然对于这篇文章探讨的问题,这样写也是错的,因为这等同于 B* addree2 = (B*)value)。

我上面评论里写的static_cast<B*>((D*)value)其实并不是那么C++的,严格来讲应该写成这个static_cast<B*>(reinterpret_cast<D*>(value))。

所以使用static_cast是不能解决这篇文章探讨的问题的。因为这和使用何种转换方式无关,原因只是在于转换过程中丢失了B与D之间的关系  回复  更多评论
  
# re: C++ 多重继承的强制类型转换。[未登录]
2011-01-13 22:48 | foxriver
@oldman
你说的很对哦,呵呵。static_cast仅仅只能有个编译警告,解决不了任何问题。  回复  更多评论
  
# re: C++ 多重继承的强制类型转换。[未登录]
2011-04-14 13:38 | cc
虚继承解决的是多继承中的同一基类只有一份拷贝。文中讨论的貌似是不同基类在多继承中的问题。  回复  更多评论
  
# re: C++ 多重继承的强制类型转换。
2012-02-01 10:38 | Evan
D的内存布局一定是A下来B,下来C,最后才是D数据成员。B* address2 = (B*)(value); 强转本来就是错误的,因为如果B中的数据结构和A中的不一致,那么下面引用到B的数据成员,肯定会崩溃。你的这个例子就是AB都是相同的内存结构,那么引用B的时候,能输出A,所以不会有问题。所以应该看看C++对象模型这本书。  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理



<2011年4月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

常用链接

留言簿(3)

随笔档案

文章档案

相册

1

搜索

  •  

最新评论

阅读排行榜

评论排行榜