C++基本功和 Design Pattern系列 Inheritance VS Delegation

http://blog.sina.com.cn/u/1261532101

首先是const 和 reference的使用,这部分内容已经在C语言里说过,但是在C++里又有了一些扩展。 

C++允许使用object作为参数传递,但是object有大有小。 比如下面一个object class:

class CBitmap{
public:
    CBitmap();
    ~CBitmap();
private:
    const static UINT32 MAX_BUFFER_SIZE = 65536;
    UINT32 m_Height;
    UINT32 m_Width;
    BYTE    m_Buffer[MAX_BUFFER_SIZE];
};

如果我们有个函数,是DrawBitmap,那么就有两种不同的声明方式。
========错误的方式========
void DrawBitmap(CBitmap Bitmap);
========正确的方式========
void DrawBitmap(CBitmap & Bitmap);
如果我们使用第一种方式,那么程序就会创建一个临时的CBitmap object,然后把Bitmap拷贝进去,传送给DrawBitmap。这可不是只会拷贝几个字节那么简单,而且CBitmap的所有内容,包括里边的m_Buffer都会拷贝。如果使用reference也就是 "&",就不会有任何操作。如果在DrawBitmap里边不会改变Bitmap的任何状态,也就是不会改变任何属性,那么就最好加上const关键字,最后的 DrawBitmap的声明应该是:
void DrawBitmap(const CBitmap & Bitmap); 

这个时候,在DrawBitmap里,只能调用CBitmap中声明为const的函数。让我们来看看代码

class CBitmap{
public:
    CBitmap();
    ~CBitmap();
    UINT32 GetHeight(void) const;
    void SetHeight(void);
private:
    const static UINT32 MAX_BUFFER_SIZE = 65536;
    UINT32 m_Height;
    UINT32 m_Width;
    BYTE    m_Buffer[MAX_BUFFER_SIZE];
};

UINT32 CBitmap::GetHeight(void) const
{
   return m_Height;
}

void CBitmap::SetHeight(UINT32 Height)
{
  m_Height = Height;
}

大家看到了,在 UINT32 GetHeight(void) const; 有个const,意思是这个函数不会改变任何CBitmap里的属性值。由于SetHeight()会改变m_Height,所以不能声明为const. 在DrawBitmap里边,由于参数是const类型,所以只能调用 const的方法。
void DrawBitmap(const CBitmap & Bitmap)
{
    Bitmap.GetHeight();        // 正确,没有问题
    Bitmap.SetHeight(100);   // 错误,Bitmap是const类型
}

值得注意的是,尽量把class的声明中,不改变属性的方法,声明为 const ,这就是所谓的良好的程序风格。

最后,如果在CBitmap里边有另外一个类,比如是CNormalMap,那么如果有个方法用来取得CNormalMap,code如下

class CBitmap {
.....
// 省略
Private: 
    CNormalMap NormalMap;
public: 
    CNormalMap GetNormalMap(void);
}

这是一种类做法,但是并没有充分的考虑效率。首先,返回的CNormalMap不是引用,这个是正确的做法,对象拷贝以后,即使改变内容,也不会影响 Class的状态。但是如果我们本来就不打算改变CNormalMap的状态呢?那么这个函数的调用效率就低下了,所以我们一般提供2个函数,代码如下:

class CBitmap {
.....
// 省略
Private: 
    CNormalMap NormalMap;
public: 
    CNormalMap GetNormalMap(void);
    const CNormalMap & GetStaticNormalMap(void);
}
或者利用C++的函数重载,做如下声明:
class CBitmap {
.....
// 省略
Private: 
    CNormalMap NormalMap;
public: 
    CNormalMap GetNormalMap(void);
    const CNormalMap & GetNormalMap(void) const;
}

如果我们不打算改变NormalMap的状态,那么就掉用GetStaticNormalMap() 或者 GetNormalMap() 的const调用,这样我们可以充分的利用reference的效率。

===============================================
C+ +真是内容多呀,一个const和reference就讲了一大堆。好了, 继续今天Design Pattern的内容。所谓 Design Pattern,翻译过来就是设计模式,是OO语言的一些基本运用。Aear会讲一些Design Pattern,并且给出在游戏中的可能的运用方式。今天第一课将会介绍Design Pattern中的两个基本概念,Inheritance 和 Delegation.

所谓Inheritance就是继承,我想学过C++的人都知道什么是继承。以上面的CBitmap为例子,如果我们想生成一个CTexture类,并且保留CBitmap的功能,比如GetBitmapHeight什么的,可以这么做:

class CTexture : public CBitmap {
public:
    CTexture();
    ~CTexture();
};

当时还有另外一种方法,并不使用继承,而是把CBitmap当做CTexture的一个成员,这就是Delegation。代码如下:

class CTexture {
public:
    CTexture();
    ~CTexture();
private:
    CBitmap InternalBitmap;
public:
    UINT32 GetHeight(void) {   return InternalBitmap.GetBitmapHeight();  };    
};

关于Inheritance和Delegation哪个更好,Aear不想在这里说,因为网上已经有太多的关于这个争论的文章。但是Aear的个人观点是能用Delegation的地方,就不要使用Inheritance。道理很简单,不同class层次的函数调用,很容易使程序员产生混乱。

举个简单的例子: 比如CTexture从CBitmap继承了GetBitmapHeight 方法,但是CTexture又提供了Bitmap的缩小功能,或者是mipmap,所以提供了一个函数 GetTextureHeight()。 只有GetTextureHeight能返回正确的 texture size的内容。 然后一个不明就里的程序员使用这个类,他发现了GetBitmapHeight这个函数,想当然的觉得是这个函数是用来取得texture的大小,那么。。。。。一切都错乱了。

所以在尽可能的情况下使用Delegation,在其他情况,比如一些Design Pattern和Interface的时候,使用Inheritance (纯个人观点).

 

重载、覆盖、多态与函数隐藏

例8-2

#include <iostream>

using namespace std;

 

class Base{

public:

         virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; }

};

 

class Derive : public Base{

public:

         void fun(int i){ cout <<"Derive::fun(int i)"<< endl; }

         void fun(double d){ cout <<"Derive::fun(double d)"<< endl; }        

};

 

int main()

{

         Base *pb = new Derive();

         pb->fun(1);//Derive::fun(int i)

         pb->fun((double)0.01);//Derive::fun(int i)

         delete pb;

         return 0;

}

 

 

9

#include <iostream>

using namespace std;

 

class Base{

public:

         virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; }

};

class Derive : public Base{

public:

        void fun(int i){ cout <<"Derive::fun(int i)"<< endl; }

        void fun(char c){ cout <<"Derive::fun(char c)"<< endl; } 

        void fun(double d){ cout <<"Derive::fun(double d)"<< endl; }        

};

int main()

{

         Base *pb = new Derive();

         pb->fun(1);//Derive::fun(int i)

         pb->fun('a');//Derive::fun(int i)

         pb->fun((double)0.01);//Derive::fun(int i)

 

         Derive *pd =new Derive();

         pd->fun(1);//Derive::fun(int i)

         //overload

         pd->fun('a');//Derive::fun(char c)         

         //overload

         pd->fun(0.01);//Derive::fun(double d)         

 

         delete pb;

         delete pd;

         return 0;

}

 

7-1和例8-1很好理解,我把这两个例子放在这里,是让大家作一个比较摆了,也是为了帮助大家更好的理解:本例的效果同例6,异曲同工。想必你理解了上面的这些例子后,这个也是小Kiss了。

n          7-1中,派生类没有覆盖基类的虚函数,此时派生类的vtable中的函数指针指向的地址就是基类的虚函数地址。

n          8-1中,派生类覆盖了基类的虚函数,此时派生类的vtable中的函数指针指向的地址就是派生类自己的重写的虚函数地址。

在例7-28-2看起来有点怪怪,其实,你按照上面的原则对比一下,答案也是明朗的:

n          7-2中,我们为派生类重载了一个函数版本:void fun(double d)  其实,这只是一个障眼法。我们具体来分析一下,基类共有几个函数,派生类共有几个函数:

类型

基类

派生类

Vtable部分

void fun(int i)

指向基类版的虚函数void fun(int i)

静态部分

 

void fun(double d)

 

我们再来分析一下以下三句代码

Base *pb = new Derive();

pb->fun(1);//Base::fun(int i)

pb->fun((double)0.01);//Base::fun(int i)

 

这第一句是关键,基类指针指向派生类的对象,我们知道这是多态调用;接下来第二句,运行时基类指针根据运行时对象的类型,发现是派生类对象,所以首先到派生类的vtable中去查找派生类的虚函数版本,发现派生类没有覆盖基类的虚函数,派生类的vtable只是作了一个指向基类虚函数地址的一个指向,所以理所当然地去调用基类版本的虚函数。最后一句,程序运行仍然埋头去找派生类的vtable,发现根本没有这个版本的虚函数,只好回头调用自己的仅有一个虚函数。

 

这里还值得一提的是:如果此时基类有多个虚函数,此时程序编绎时会提示调用不明确。示例如下

#include <iostream>

using namespace std;

 

class Base{

public:

         virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; }

                    virtual void fun(char c){ cout <<"Base::fun(char c)"<< endl; }

};

 

class Derive : public Base{

public:

    void fun(double d){ cout <<"Derive::fun(double d)"<< endl; } 

};

 

int main()

{

         Base *pb = new Derive();

                    pb->fun(0.01);//error C2668: 'fun' : ambiguous call to overloaded function

         delete pb;

         return 0;

}

好了,我们再来分析一下例8-2

n          例8-2中,我们也为派生类重载了一个函数版本:void fun(double d)  ,同时覆盖了基类的虚函数,我们再来具体来分析一下,基类共有几个函数,派生类共有几个函数:

类型

基类

派生类

Vtable部分

void fun(int i)

void fun(int i)

静态部分

 

void fun(double d)

 

从表中我们可以看到,派生类的vtable中函数指针指向的是自己的重写的虚函数地址。

 

我们再来分析一下以下三句代码

 

Base *pb = new Derive();

pb->fun(1);//Derive::fun(int i)

pb->fun((double)0.01);//Derive::fun(int i)

 

第一句不必多说了,第二句,理所当然调用派生类的虚函数版本,第三句,嘿,感觉又怪怪的,其实呀,C++程序很笨的了,在运行时,埋头闯进派生类的vtable表中,只眼一看,靠,竞然没有想要的版本,真是想不通,基类指针为什么不四处转转再找找呢?呵呵,原来是眼力有限,基类年纪这么老了,想必肯定是老花了,它那双眼睛看得到的仅是自己的非Vtable部分(即静态部分)和自己要管理的Vtable部分,派生类的void fun(double d)那么远,看不到呀!再说了,派生类什么都要管,难道派生类没有自己的一点权力吗?哎,不吵了,各自管自己的吧^_^

 

!你是不是要叹气了,基类指针能进行多态调用,但是始终不能进行派生类的重载调用啊(参考例6)~~~

 

再来看看例9,

 

(篇幅所限,未完,待续)

 

 原文地址 http://blog.csdn.net/callzjy/archive/2004/01/04/20042.aspx

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目 录 序言 前言 读者指南 第1章 引言 1 1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象接口 9 1.6.4 描述对象的实现 10 1.6.5 运用复用机制 13 1.6.6 关联运行时刻和编译时刻的 结构 15 1.6.7 设计应支持变化 16 1.7 怎样选择设计模式 19 1.8 怎样使用设计模式 20 第2章 实例研究:设计一个文档编 辑器 22 2.1 设计问题 23 2.2 文档结构 23 2.2.1 递归组合 24 2.2.2 图元 25 2.2.3 组合模式 27 2.3 格式化 27 2.3.1 封装格式化算法 27 2.3.2 Compositor和Composition 27 2.3.3 策略模式 29 2.4 修饰用户界面 29 2.4.1 透明围栏 29 2.4.2 Monoglyph 30 2.4.3 Decorator 模式 32 2.5 支持多种视感标准 32 2.5.1 对象创建的抽象 32 2.5.2 工厂类和产品类 33 2.5.3 Abstract Factory模式 35 2.6 支持多种窗口系统 35 2.6.1 我们是否可以使用Abstract Factory 模式 35 2.6.2 封装实现依赖关系 35 2.6.3 Window和WindowImp 37 2.6.4 Bridge 模式 40 2.7 用户操作 40 2.7.1 封装一个请求 41 2.7.2 Command 类及其子类 41 2.7.3 撤消和重做 42 2.7.4 命令历史记录 42 2.7.5 Command 模式 44 2.8 拼写检查和断字处理 44 2.8.1 访问分散的信息 44 2.8.2 封装访问和遍历 45 2.8.3 Iterator类及其子类 46 2.8.4 Iterator模式 48 2.8.5 遍历和遍历过程中的动作 48 2.8.6 封装分析 48 2.8.7 Visitor 类及其子类 51 2.8.8 Visitor 模式 52 2.9 小结 53 第3章 创建型模式 54 3.1 Abstract Factory(抽象工厂)— 对象创建型模式 57 3.2 Builder(生成器)—对象创建型 模式 63 3.3 Factory Method(工厂方法)— 对象创建型模式 70 3.4 Prototype(原型)—对象创建型 模式 87 3.5 Singleton(单件)—对象创建型 模式 84 3.6 创建型模式的讨论 89 第4章 结构型模式 91 4.1 Adapter(适配器)—类对象结构型 模式 92 4.2 Bridge(桥接)—对象结构型 模式 100 4.3 Composite(组成)—对象结构型 模式 107 4.4 Decorator(装饰)—对象结构型 模式 115 4.5 FACADE(外观)—对象结构型 模式 121 4.6 Flyweight(享元)—对象结构型 模式 128 4.7 Proxy(代理)—对象结构型 模式 137 4.8 结构型模式的讨论 144 4.8.1 Adapter与Bridge 144 4.8.2 Composite、Decorator与Proxy 145 第5章 行为模式 147 5.1 CHAIN OF RESPONSIBIL ITY(职责链) —对象行为型模式 147 5.2 COMMAND(命令)—对象行为型 模式 154 5.3 INTERPRETER(解释器)—类行为型 模式 162 5.4 ITERATOR(迭代器)—对象行为型 模式 171 5.5 MEDIATOR(中介者)—对象行为型 模式 181 5.6 MEMENTO(备忘录)—对象行为型 模式 188 5.7 OBSERVER(观察者)—对象行为型 模式 194 5.8 STATE(状态)—对象行为型模式 201 5.9 STRATEGY(策略)—对象行为型 模式 208 5.10 TEMPLATE METHOD(模板方法) —类行为型模式 214 5.11 VISIT
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值