C++基本功和 Design Pattern系列(2) Type Cast, InterfaceInheritance VS Implementation Inheritance
====================================================================
又到周末了,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,下次见!