游戏开发论坛

 找回密码
 立即注册
搜索
查看: 21680|回复: 21

C++基本功和 Design Pattern系列(2) Type Cast, Interface Inheritance VS

[复制链接]

27

主题

179

帖子

259

积分

中级会员

Rank: 3Rank: 3

积分
259
发表于 2006-10-29 14:13:00 | 显示全部楼层 |阅读模式
======================================================
原创就是麻烦呀,最怕误人子弟....大家请把我的文章当参考,详细内容  
还请参照 权威书籍 <c++ programming language>如果文中有错误和遗
漏,请指出,Aear会尽力更正, 谢谢!
======================================================

又到周末了,Aear在此感谢大家坐这么近来听我说书。今天讲的是C++的类型转换(比较无聊的内容,但最好看看,因为可以帮助大家减少程序中的bug)。许多学过C的朋友一定还记得C语言中的类型转换,例如:

float FloatNum = 1.234;
int IntNum = (int)FloatNum;
// IntNum = 1

这是比较正常的类型转换,稍微危险一点的转换如下:

float FloatNum = 1.234;
float * pFloatPointer = &FloatNum;
int * pIntPointer = (int *)pFloatPointer;
// *pIntPointer 里边就是乱七八糟的东西了

C的类型转换虽然很方便,但是却带来了更多的问题。比如 C的类型转换可以允许你进行任意类型之间的转换。 非常随意的使用类型转换很容易造成程序逻辑的混乱,使人看不懂你写的代码,或者编译器不能正确识别你的转换的意图,所以做出错误的转换方式。其次,C类型的转换很难查错。特别是在大型的工程中,你想找出一个因为 (uint)转换成 int而产生益出的问题,可能需要查看上千行包含"(int)"的代码。为了避免诸如此类情况的发生 C++引入了4种类型转换的方式。请记住,C的类型转换只是为C语言设计的,并不适合C++。C++支持C的类型转换只不过是为了向下兼容的考虑。

让我们来看看C++的4种类型转换(关于这4种类型转换的详细说明,请参见<C++ Programming Language> 3rd Edition):

static_cast<>()
dynamic_cast<>()
const_cast<>()
reinterpret_cast<>()

我们一个一个来说
================== static_cast ==================
static_cast<>()
static_cast可以用来进行相关类型之见的转换,比如double 转成 float, float转成 int 以及 有关联的pointer之间,有关联的 class pointer 之间的转换。

========比如========

float FloatNum = 1.234;
int IntNum = static_cast<int>(FloatNum);    // IntNum = 1;

========或者========

class BaseClass{
public:
    BaseClass();
    virtual ~BaseClass();
};

class DerivedClass : public BaseClass{
public:
    DerivedClass();
    ~DerivedClass();
    void DoSomething(void);
};

BaseClass * pBaseClass = new BaseClass();
// 没问题,不过call pDerviedCalss->DoSomething()很有可能会crash
DerivedClass * pDerivedClass = static_cast<DerivedClass *>pBaseClass;

DerivedClass * pDerivedClass = new DerivedClass();
// 没问题,很安全
BaseClass * pBaseClass = static_cast<BaseClass *>(pDerivedClass);

值得注意的是 static_cast是在程序编译的时候检查类型转换是否符合要求,并不在程序运行期间进行检查,因为没有runtime overhead, 对速度的影响比较小。所以在你对类型转换很有把握的时候,可以尽量的使用static_cast。另外 static_cast在转换指针的时候并不能保证转换前的指针地址和转换后的指针地址相同,特别是在多重继承的类结构中,指针地址经常会变化。

下面是一些通常比较危险的类型转换,

========包括========
unsigned 转 sign 比如 uint 转成 int
double 转 float
long 转 int (64位操作系统有危险,32位无)

这些转换都是值域大的转成值域小的数,或者无符号转成有符号。比如:

unsigned int k = 4294967290;
int m = static_cast<int>(k);

这里k已经超出了int的值域,所以 m的最后结果是 -6。所以在做以上的类型转换的时候,要特别的小心。


================== dynamic_cast ==================
dynamic_cast是用来对相关的class 指针之间进行的类型转换。由于dynamic_cast在运行过程中对转换进行安全性检查,所以在很大程度上影响程序的运行速度,并且在便宜的时候需要打开runtime type info 的开关 /GR,所以并不推荐使用。

如果要使用dynamic_cast那么要求转换的类至少需要含一个虚函数,并且只能对类的指针进行转换操作,指针不包括 void *。 例如:

class BaseClass{
public:
    BaseClass();
    virtual ~BaseClass();
    virtual void DoSomething(void);
};

class DerivedClass : public BaseClass{
public:
    DerivedClass();
    ~DerivedClass();
    void DoSomething(void);
};

DerivedClass * pDerivedClass = new DerivedClass();
// 没问题
BaseClass * pBaseClass = dynamic_cast<DerivedClass *>(pDerivedClass);

BaseClass * pBaseClass = new BaseCalss();
// 有问题,基类转成派生类,dynamic_cast会返回null 所以 pDerivedClass == NULL
DerivedClass * pDerivedCalss = dynamic_cast<DerivedClass *>(pBaseClass);

================== const_cast ==================
顾名思义,就是把const变量转换成 non-const变量,或者把volatile转成non-volatile。这是最不推荐使用的一种转换,只有在特殊的情况下才会使用。如果你需要大量的使用const_cast,那么只能说明程序中存在着设计缺陷。

const_cast的使用如下:
float FloatNum = 12;
const float * pConstFloatPointer = &FloatNum;
// ok 这么用没问题
float * pFloatPointer = const_cast<float *>(pConstFloatPointer);
// 编译错误,const_cast只能进行const和non-const之间的转换
int * pFloatPointer = const_cast<int *>(pConstFloatPointer);

================== reinterpret_cast ==================
reinterpret_cast是最危险的一种转换类型,它并不对数据进行任何实际的转换操作,而是直接把数据当作另一种类型来使用(跟内存拷贝的功能差不多)。比如:

class ClassX{...};
class ClassY{...};

ClassX * pClassX = new ClassX();
// 没问题,不过除非classX和classY都是结构相同的interface,否则并不安全,程序很容易crash
ClassY * pClassY = reinterpret_cast<ClassY *>(pClassX);

reinterpet_cast比较有用的地方就是函数指针的转换,比如把一个指针存到一个函数指针数组中:

      typedef void (*FuncPtr)();
      FuncPtr FuncPtrArray[10];
      int DoSomething() {...};
      // 这里使用reinterpret_cast
      FuncPtrArray[0] = reinterpret_cast<FuncPtr>(&DoSomething);

总的来说,尽可能的使用这4种转换来清晰的表达你转换的目的,尽量不要使用C风格的转换。

================== Design Pattern ==================
今天的Design Pattern讲讲另外两个概念: Interface Inheritance 和 Implementation Inheritance。

上次Aear已经介绍了Inheritance 和 Delegation,并且说如果能用Delegation的地方,就最好不要使用Inheritance。但是如果必须使用Inheritance怎么办?因此就出现了不同使用Inheritance(继承)的方式。第一种就是 Interface Inheritance。

简单的说,Interface Inheritance就定义一个abstract class (抽象类),把所有提供的类方法都在这个抽象类中定义成纯虚函数,然后在派生类中实现这些虚函数。对于这个abstract class,我们可以称它为: Interface。 (是不是有点象 COM?)。 比如在游戏中,所有的敌人都可以移动和攻击,但是不同的敌人类型,攻击和移动的具体方式都不一样。我们就可以用一个抽象类在表示所有的敌人,在派生类中具体实现不同移动和攻击方式。
下面让我们来看看代码:

class Enemy{
public:
    virtual void Move(void) = 0;
    virtual void Attack(void) = 0;
};

// 人类敌人
class HumanEnemy : public Enemy{
public:
    void Move(void) {...}; // 用2条腿走路
    void Attack(void) {...}; // 用拳头或武器攻击
};

// 怪物敌人
class MonsterEnemy : public Enemy{
public:
    void Move(void) {...}; // 用4跳腿走路
    void Attack(void) {...}; // 用牙齿和爪子攻击
};

在运行AI的时候我们不需要具体判断对方是人类还是敌人,直接调用 move和attack就行了,代码如下:

void CalculateAction( Enemy * pEnemy)
{
    pEnemy->Move();
    pEnemy->Attack();
}

main()
{
   ....
   HumanEnemy * pHumanEnemy = new HumanEnemy();
   MonsterEnemy * pMonsterEnemy = new MonsterEnemy();
   CalculateAction(static_cast<Enemy *>pHumanEnemy);
   CalculateAction(static_cast<Enemy *>pMonsterEnemy);
   ....
}

=========== 小分割线 ===========

关于implementation inheritance,就是Aear在上一章里举的关于CBitmap和CTexture的例子。其中CBitmap只基类,提供了 GetBitmapHeight()方法。 CTexture是CBitmap的派生类,提供了GetTextureHeight()的方法。这种结构很容易使人产生程序逻辑结构的混乱,并不推荐使用。Aear的个人观点是:
    能使用 Delegation就用
    不能用Delegation就用 Interface Inheritance
    用不了Interface Inheritance就重新设计你的类结构,使之能使用前两种方式
    如果重新设计系统或者使用Interface Inheritance开销太大,再用Implementation Inheritance。

其实所有的Implementation Inheritance都可以通过Interface Inheritance来实现,具体来说,就是定义一个abstract class基类,在此基础上,定义派生类的abstract class (派生类也是interface),如此定义下去,直到所有需要实现的类都拥有自己的interface class为止。

好了就说这么多了,大家有空去我的blog坐坐Aear's Blog,下次见!





61

主题

1429

帖子

1430

积分

金牌会员

Rank: 6Rank: 6

积分
1430
发表于 2006-10-29 16:14:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(2) Type Cast, Interface Inheritance

这么容易就上精华了!!!

187

主题

6490

帖子

6491

积分

论坛元老

团长

Rank: 8Rank: 8

积分
6491
发表于 2006-10-29 16:36:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(2) Type Cast, Interface Inheritance

第二篇终于来了!!收藏了,谢谢楼主。

8

主题

716

帖子

716

积分

高级会员

Rank: 4

积分
716
发表于 2006-10-30 10:05:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(2) Type Cast, Interface Inheritance

1. 感谢aear的文章
2. 精华应该是sea_bug加的,8是我
3. aear对于c++中的继承所举的例子如BaseClass和DerivedClass需要注意一些重要的细节,既然你允许BaseClass * pBaseClass = dynamic_cast<DerivedClass *>(pDerivedClass);那么BaseClass的dtor必须为virtual. 其问题不在于派生继承,其反例有c++ std中的class unary_function,但其下派生类不存在基类指针指向派生类对象的情况,故不用.

对于以上这些很重要的基础条款,我依然建议大家去看大师所著的书籍,吾辈资质有限,恐有遗漏,对后来者难免有误人之嫌(aear毋多心),只当读书遇到理解上的问题,再拿来作讨论.

27

主题

179

帖子

259

积分

中级会员

Rank: 3Rank: 3

积分
259
 楼主| 发表于 2006-10-30 10:56:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(2) Type Cast, Interface Inheritance

谢BZ提醒,不过根据Aear的个人理解,Dynamica_cast只需要在转换的类中包含Virtual Function即可,并不需要强调 Virtual Destructor. 而Virtual Destructor主要是为了Derived class的destructor能够正常的执行。  不过无论如何,base class的destructor应该为Virtual。是Aear疏忽了,已经修改原文,再次感谢BZ。

89

主题

4036

帖子

4132

积分

论坛元老

Rank: 8Rank: 8

积分
4132
发表于 2006-10-30 11:14:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(2) Type Cast, Interface Inheritance

Dynamica_cast其实的实现还是挺复杂的。跟踪汇编即可知道。
还有就是Dynamica_cast必须依赖rtti信息。不打开compiler的rtti。dynamic_cast是会crash掉的。

4

主题

66

帖子

66

积分

注册会员

Rank: 2

积分
66
发表于 2006-10-30 11:27:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(2) Type Cast, Interface Inheritance

class Enemy{
public:
    virtual void Move(void) = 0;
    virtual void Attack(void) = 0;
};

/////////////////////////
我觉得还是写成一个
virtual void prosess(void)  = 0;

每个类都继承有prosess().更好的话,建议分成prosessLogic和prosessDisplay
逻辑处理和显示,而LZ写的MOVE,ATK,则应该写在prosessLogic里

这样main里只用写
while()
{
   prosessLogic();
   prosessDisplay();
}

8

主题

716

帖子

716

积分

高级会员

Rank: 4

积分
716
发表于 2006-10-30 14:06:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(2) Type Cast, Interface Inheritance

针对楼上的
virtual member function不应该直接放在public中,此属于不良设计
而应该视情况放在protected或private中
再提供public的non-virtual member function以供外部操作

详细请参见《Exceptional C++ Style》17、18、19章

4

主题

66

帖子

66

积分

注册会员

Rank: 2

积分
66
发表于 2006-10-30 14:12:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(2) Type Cast, Interface Inheritance

回马肝大大~~~..


我的意思是写在基类里.上面那个有public的是楼主写的不是我写的.................

8

主题

716

帖子

716

积分

高级会员

Rank: 4

积分
716
发表于 2006-10-30 14:13:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(2) Type Cast, Interface Inheritance

re aear:
这里我没有针对xxx_cast而言
而是指当允许用base pointer去指向一块derive memory时
当base class的dtor不是virtual时
client有可能会使用delete pBase
这时会导致调用不完全,因为只有base的部分被destroy了
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2026-1-25 20:48

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表