多态之浅谈

一:组合: 将一个类型x的对象作为公共的对象嵌入到新类型y的对象中间。
这样做是很有意义的:
①.他意味着x可以成为y的内部实现的一部分,对于新类则不必专门写出模拟x这个嵌入对象的接口,因为直接调用方法操作x对象来的直接有效容易的多。不需要你模拟x的方法还要专门为它设置成员,结构也不清晰。
例如class cpu
{
public:
 cpu();
 ~cpu();
 void cacultor() ;
    void IsOk();
 ...
private:
 ...
};
//computer不能继承cpu,cpu只是它的独立一部分
class computer
{
public:
 computer();
 ~computer();
 void DoCaculator() { Mycpu.cacultor(); };//操作cpu对象使用其方法
 ...
private:
 cpu Mycpu;
};
②使用对象内嵌自定义构造函数可以消除一些隐式的类型转换。当然你也可以使用explicit来显式声明构造函数避免他被你“无意识情况下调用成为c++完美支持自定义类型牺牲品”。
举个例子:
 class Example
{
public:
 ...
 Example( int i ) : m_Member(i) {};
 friend BOOL operator == ( Example A, Example B );
 BOOL DoSomething( Example& A )
 {
  return (A == m_Member); //这里虽然没有匹配==操作的类型,但是编译器
                          //会发现构造函数 可以将int转化为可以比较的对象形式
                          //它就去做而不管你是否可能因为手误或是什么
                          //结果可想而知了^_^
 };
 ...
private:
 int m_Member;
};
对于以上隐式转化的问题稍微提一下,可以用explicit消除,也可以使用内嵌单变量对象构造函数来减少自动隐士转化的概率。随便说一下多元构造函数不会带来隐士转化的问题。
③关于嵌套使用一些扩充:
关于嵌套的向前声明:除了定义类完毕,在其他类中定义其对象或指针类型变量外,还可以这样
直接定义:
class A
{
 class B;
 B* p;
 ...
};

class A::B
{
 //定义,这样类似的函数体定义可以减少头文件的编译信息
    //如果直接在头文件中定义B那么变的比较庞大
};

二:
①继承:被继承类成为父类,继承父类的类子类。子类具有父类的所有方法和成员。
语法使用 子类 [冒号] [继承类型] 父类,
继承类型:
1.公有继承 public 不多说了
2.私有继承 private:仅仅为了语法的完整性。和组合处理包含对象的方法有点类似。 需要直接调用父类的方法/
class Dad
{
public:
 void Fun1() ;
 void Fun2() ;
private:
 ...
};
class Son:Dad
{
public:

Dad::Fun1();  //需要显式声明一下才能当作使用
 Dad::Fun2();  //如果直接在子类中调用父类方法那就有问题
};
3.保护继承: protect 是c++中一个相对重要的语法,用法比private宽松点,允许在子类中使用父类的方法。
②抽象类:含有纯虚函数的类称为抽象类,virtual fun() = 0; 抽象类不能实力化.。它仅仅用来抽象出一层方法描述,不必实现,所以对其实例化没有任何意义。
如果我们不希望在类里出现某个对象的表现,利用抽象类可以推迟描述知道其某个派生类中。例如:描述T*指针的集合
template <class T>
class set
{
public:
 virtual void insert(T*) = 0;
 virtual void remove(T*) = 0;
 virtual ~set(){}
 };
在当前类中不会出现任何实现上的细节,上面只是提供了一个如何使用当前类的一些信息。而不用担心抽象类中到的T到底是为何种类型。所有使用方法豆油抽象类给出了,使用起来十分方便。降低了用户和实现者之间的偶合度,实现者可以基于抽象类给出的信息,实现他的需求,而不管实现者怎么去实现,对于用户来说使用都是一样的,使得用户和实现者之间划分的更为清晰。
③ 现在该轮到最重要的问题---多态
   首先还是表述一些基本概念:
1. 捆绑:把函数体与函数调用相联系起来称为捆绑
2. 早捆绑:捆绑在程序之前完成,函数调用是唯一的,一般c中采用这种方法。
3. 晚捆绑:如果无法确定调用则必须到运行时确定对象类型,这种称为晚捆绑。
所要叙述的中心:
===============================================================================
4.虚函数:c++中声明函数前加上virtual关键字,基类为虚函数在派生类中一定为虚,对于虚函数先介绍一下它的功能,简单的说运行时进行类型的识别使得子类以父类类型使用(放宽参数)时可以顺利的找到心中想要调用的方法。
例如
#include "iostream"
using namespace std;
class Instrucment
{
public:
 virtual void play() { cout<<"Instucment::play()"<<endl;}
 //...
};

class wind:public Instrucment
{
public:
 void play() { cout<<"wind::play()"<<endl; }  //请看这里不是声明为virtual
 //...
};

void tune(Instrucment& object )
{
 object.play();
}
如果在这里这么调用
void main()
{
 wind object1;
 tune( object1 ); //我们可以得到希望的wind中的play方法而不是instrucment中的。
}

这到底如何实现这一功能的呢?
在讨论之前先请看wind中的play方法中的注释,大家应该注意到了在子类中play方法并没有声明为virtual为何也能当作虚函数一样使用并能对应找到这个方法呢?
① 放宽覆盖原则:
这是c++设计的采用的一个对于函数覆盖的一个处理方法,基类中所有virtual的方法可以被同名同参的子类中的方法所覆盖,不改变virtual性质。
这实际中是有用的
假设没有这个原则,那么Instrucment 中的virtual void play()将不会被子类中的void play()方法所覆盖,则在子类的方法中则变成:
1. 从 Instrucment中继承下来的virtual void play()方法
2. 自身定义的 void play()方法
在函数调用过程中对object1进行运行时对象类型识别后为wind类型找到wind类中的虚表及其方法的偏移找对应函数,不过可惜,因为这个函数没有被wind中的play所覆盖,调用永远也不可能找到wind中你期待的方法,除非你显式的声明为virtual。
② 提到放宽覆盖原则不得不提到另外一个放宽参数原则
一般情况记住派生类可以当做基类的类型到处使用。是对上面的一个补充

回到正题:
为什么instrument类型的wind对象可以找到自己的类型的方法呢?
①首先c++中所有声明为virtual的函数的类均会产生一张虚表VTBL,顾名思义VTBL通常是一张存放那些声明为virtual的函数指针的数组(有些编译器会处理为链表形式,不过目的都是一样的),其中指针的条目就是该class中所有virtual函数的数目(包括从基类继承下来的virtual但不包括基类的虚析构函数—记住构造和析构函数是不能被继承的虚机制对其不起作用)
class Base
{
public:
 virtual ~Base() ;
 virtual void f1();
 virtual void f2();
 virtual void f3();

};
那么 Base中虚表内的函数指针分布如下
Base’s VTBL:
1--------------------------à Base::~Base()
2---------------------------> Base::f1()
3--------------------------à Base::f2()
4--------------------------à Base::f3()
如果有Derived继承于Base则在继承类中的VTBL的分布:所有虚函数指针位置按照声明先后依次拍放完毕至第一个出现被Derived覆盖的虚函数位置,然后拍放从类继承下来的虚函数(覆盖后位置不变)然后拍放后面的虚函数。
class Derived:public Base
{
public:
 virtual ~Derived();
 virtual void f0();
 virtual void f2();
 virtual void f4();

};

那么在Derived中位置拍放如下
1----------------------------à Derived::~Derived()
2----------------------------à Derived::f0()
3----------------------------à Base::f1()  //基类的virtual函数
4----------------------------à Derived::f2()//覆盖后的
5----------------------------à Base::f3()
6----------------------------à Derived::f4()

每个类中只要有virtual函数存在就会产生一张虚表,如果很多这样的类VTBL也会站用不少的资源哦。这么多VTBL我们怎么处理它的放置位置呢?我记得不清楚了好象叫探勘还是勘探法的:将VTBL放在内含非内联,非纯虚的虚函数的声明的位置处。
但是这样如果你将虚函数声明为内联有可能产生问题,所有的虚函数都声明为inline这种方法将会失败,怎么办呢?一般编译器为保证这种方法可行性,会忽略你的声明的虚函数的是否内联标志,一概采用为不内联。

② 当然仅仅保存一份虚表对于你想要完成整个寻找工作还有很远,我们需要一个能找到虚表的东西,要不光有虚表是没有有用,我们要想办法找到它。于是一个特殊的成员被隐蔽的加在虚函数声明的类中,他就是指向虚表的指针VTPR,这个指针一般起始指向VTBL的首位,有了这个指针我们可以轻松通过虚函数的在虚表中偏移量轻松找到对应函数。
Base对象:

 Base::~Base()
                                        Base::~f1()//VPTR+OFFSET(f1在VTBL偏移)
                                      
                       ….

Base* p->f1()的调用转化成 (Base* p-> VPTR[i] )(p) //这里获得第I个个VTBL中函数,p当作this
//入该函数。便于找到当前对象中的虚表

③ 这样我们看起来好象已经完成了理想当中的虚函数的寻找,好象都是没有问题的。但是如果这个对象从Base继承过来的Derived类型的呢?把他当作基类使用时那该如何寻找到正确的类中的虚表呢?这就需要VTBL加入另外一些东西来帮助运行时识别对象类型 Type_Info。

对象类型信息的保存:对于每个虚函数的类,将生成一个对应该类类型的对象信息类型对象(比较拗口哦),这些对象不必是唯一的,当然好的实现可能唯一生成type_info对象而且只对那些实际用到RTTI的对应生成type_info对象,一般的把对象类型信息对象放在VTBL之后, Strustup在设计虚表的时候保留了2个空间,这个时候派到用处了。正好可以用来存放这些类型类型对象信息。^_^。
这个时候对象布局形式简单如下:

                       
VTBL
此时我们就可以比较完整的完成多态这个两个字的概念。
多态:VTPR + VTBL + Type_Id(Type_Info对象类型的一个影射)
能够保证编译器正确的找到对象的方法。
总的来说 :一个对象只要有虚函数,则上面的三个东东都会被构造出来,用VPTR和type_info找到对应的正确的VTBL,然后由VTPR+方法的偏移得到方法的地址,传给当前指针this,这样就可以正确的调用了。

04.10.2 2:00PM

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值