首页 > 代码库 > Coding之路——重新学习C++(10):类的层次结构

Coding之路——重新学习C++(10):类的层次结构

1.多重继承

  (1)多重继承一直是C++中让许多人诟病的机制,不过它大大增加了类的层次结构的灵活性,先看一个简单的例子:

class Task{public:    virtual void pending() = 0;    //...};class Displayed{public:        //...    virtual void draw() = 0;};class Satelite:public Task, public Displayed{public:        //...    void pending();    void draw();};

这样就能保证,在Satelite被当做Task或者Displayed时,使用的仍然是Satelite::pending()和Satelite::draw()。

  (2)歧义性解析。当两个基类中可能有同样名字的成员函数,我们可以在派生类中定义新的函数,让信息局部化,解决这个问题:

class Task{        //...    virtual debug_info* get_debug();};class Displayed{    //...   virtual debug_info* get_debug();};class Satelite:public Task, public Displayed{    //...     debug_info* get_debug(){        debug_info *dip1 = Task::get_debug();        debug_info *dip2 = Displayed::get_debug();        return dip1->merge(dip2);    }};

而且某个函数在派生类中找不到,那么编译器会到它的基类中去找。

  (3)使用声明和继承。当我们希望能够基于实际参数去选择调用的函数时候,我们可以用使用声明将函数引进作用域:

class A{public:    int f(int);    char f(char);};class B{public:        double f(double);};class AB:public A,public B{public:    using A::f;    using B::f;    char f(char);    //覆盖了A::f(char)    AB f(AB);};void g(AB &ab){    ab.f(1);            //A::f(int)    ab.f(a);          //AB::f(char)    ab.f(2.0);          //B::f(double)    ab.f(ab);           //AB::f(AB)}

注意,在一个类定义中的使用声明所引用的必须是基类的成员,而且使用指令不能出现在类定义里,也不能对一个类使用。

  (3)虚基类。在一个继承体系中,用virtual刻画的虚基类,将总是用这个类的同一个对象表示,没有virtual的基类则每个基类都有一个自己的子对象。我们可以只从最终派生类中调用虚基类的函数,让类的层次更加清晰:

class Window{    virtual void draw();};class Window_border: virtual Window{    void own_draw();    void draw();};class Window_menu: virtual Window{    void own_draw();    void draw();};class Clock: public Window_border, public Window_menu{    void own_draw();    void draw();};//draw函数实现void Window_border::draw(){    Window::draw();    own_draw();}void Window_menu::draw(){    Window::draw();    own_draw();}void Clock::draw(){    Window::draw();    Window_border::own_draw();    Window_menu::own_draw();    own_draw();};

我们还可以用几个派生类为实现一个虚基类给出的界面服务,例如Window类中有函数set_color()和prompt()两个纯虚函数,那么我们可以让Window_border类实现set_color()函数,Window_menu类实现prompt()函数。如果两个类覆盖了同一个基类函数,但它们又不互相覆盖,这个类层次结构就是错的。

2.访问控制

  (1)访问控制规则:默认是private基类

  ——如果B是private基类,那么B的public和protected成员就成为了派生类D的private成员,只有D的成员和友元能将D*转化为B*。

  ——如果B是protected基类,那么B的public和protected成员就成为了派生类D的protected成员,只有D以及D的派生类的成员和友元能将D*转化为B*。

  ——如果B是public基类,派生类D中的访问控制和B中的一样。

  (2)不能通过使用声明来获取更多的访问权限,一切都要遵循访问控制规则。

3.运行时类型信息

  (1)dynamic_cast是动态类型转换操作的专长是处理那些编译器无法确定转换正确性的操作。dynamic_cast转换当对象具有所期望类型时返回一个合法的指针,否则返回空指针。

  (2)dynamic_cast要求被转换的类型必须是一个拥有虚函数的引用或者指针,这样要求,能使我们很容易为寸处对象的类型信息找到一个位置。一种典型的实现方式是吧一个“类型信息对象”附加在对象上,采用的方式是在这种对象的虚函数表里放一个指向类型信息的指针:

  (3)dynamic_cast的目标类型不必是多态的。这样我们可以把一个具体类型包裹进一个多态类型里,比如将其穿过一个I/O系统,而后“打开包裹”取出其中的具体类型:

class Io_obj{    virtual Io_obj *clone() = 0;};class Io_date: public Date, public Io_obj{};void f(Io_obj *pio){    Date *pd = dynamic_cast<Date*>(pio);}

另外,到void*类型的dynamic_cast可以用于多态类型的对象的起始地址的确定。

  (2)对于引用类型的dynamic_cast的结果隐式的有dynamic_cast本身去实现。如果类型转换失败,就会抛出一个bad_cast异常。

  (3)对于static_cast来说,它不能像dynamic_cast那样从虚基类强制转换。dynamic_cast也因为编译器不能对void*所指向的存储提供保证而不能从void*进行转换。不过两者都得遵循访问控制规则。

  (4)typeid()用于取得一个对象,该对象代表着对应运算对象的类型,它返回一个type_info类型的引用,在<type_info>中定义,简单的实现如下:

class type_info;const type_info& typeid(type_name) throw();const type_info& typeid(expression) throw(bad_typeid);class type_info{public:        virtual ~type_info();    bool operator==(const type_info&) const;    bool operator!=(const type_info&) const;    bool before(const type_info&) const;    const char* name()const;//禁止复制private:    type_info(const type_info&);    type_info& operator=(const type_info&);    //...};

  (5)一般情况下正确使用RTTI大多是服务性的代码由一个类表示,用户又希望通过派生为这个类增加功能,我们使用RTTI 转换成附加功能的类型调用附加功能。

4.成员指针和自由存储

  (1)一个指向成员的指针并不像指向一个变量或者指向一个函数的指针,它并不是一个指向一块内存的指针。这种指针更像是一个结构里的偏移量,或者一个数组的下标。我们可以安全的将一个指向基类成员的指针赋值给一个指向派生类成员的指针,反过来是不行的。静态成员函数不跟任何对象关联,因此指向静态成员的指针就是一个常规指针。

  (2)在自由存储中,operator new()和operator delete()默认是static成员,没有this指针,不会修改任何对象。另外,我们不能构造虚构造函数,但是我们可以构造一个产生类的虚函数,然后调用想用的构造函数。

Coding之路——重新学习C++(10):类的层次结构