C++虚拟继承

来源:互联网 发布:移动4g网络怎么开通 编辑:程序博客网 时间:2024/06/06 19:07

C++虚拟继承


      这篇文章主要讨论了C++中有关“不能被继承的类”,并对虚拟继承进行了分析。

 

注意:下面的代码在VC2003上编译过,但在VC2005Beta版上会有问题。我想可能是试用版上有bug吧。

 

引出问题:

 

前天,一个同事给我看了一段代码,问我这段代码的涵义。乍一看我没有看出明堂。后来在与几个同事一起深入研究后发现了它的奥妙。这其中涉及到一些C++中的高级技术,很有意思。我把我们的分析作了一个总结,借这块宝地,拿出来供大家共同学习。原始代码如下:

 

//file name: NoInherit.h

template <class T> class NoInherit_;

 

template <class T,template<class > class A >

class NoInherit_< A<T> >

{

private:

      friend A<T>;

      friend T;

      NoInherit_(){}

};

 

template <class T>

class NoInherit:virtual public NoInherit_< NoInherit< T> >

{

public:

      typedef T type;

private:

      friend T;

      NoInherit():NoInherit_< NoInherit< T> >()

      {}

};

 

如果你是大虾一下子就看明白了这段代码,那么这篇文章对你就没有多大价值了,兄弟就不敢再耽搁你的时间了。如果你不能一下子看出来,那就让我来告诉你吧。它是一个不能被其它类继承的类模板。不信你试试……

 

按照下面的方法使用这个模板:

 

class CA:NoInherit<CA>

{

      //……

};

 

这个类CA不能再被其它类继承了.像这样

Class CB:CA

{

 

}

//error……编译会出错。提示不能访问NoInherit_的私有构造函数。

 

那么这个类模板是怎么样达到“不让其它类继承”的目的的呢?也许你已经看出来了。它是通过私有构造函数和虚拟继承来实现的。在我分析这个模板的过程中我思考过下面这几个问题:

1、 有哪些方法能实现“不让其它类继承”呢?

2、 为什么要用到friend A<T>;

3、 虚拟继承是怎么一回事?为什么在这里要使用虚拟继承呢?

4、 NoInherit_中friend A<T>好理解,friend T怎么理解?

下面我就这几个问题展开讨论。

 

有哪些方法实现“不让其它类继承”

      

我们知道,子类继承父类,肯定会做的事情是:调用父类的构造函数(注意:子类不一定会调用父类的析构函数。为什么?自己想去吧!)。我们只要父类的构造函数是private的,子类想继承,没门儿!

      “不让其它类继承”就这么简单。是的,就这么简单!

      看官一定会问,既然这么简单,上面的代码为什么那么复杂呢?别急别急,待我下面慢慢道来……

      我们先分析下面几种情况:

A)单件(single)模式

//single.h

class CSingle

{

public:

static CSingle *CreateInstance() ;

static void DeleteInstance() ;

……

private:

static CSingle *m_pSingle;

Csingle(){};

Csingle(Csingle&){};

};

 

//single.cpp

#include “single.h”

CSingle * CSingle ::m_pSingle = NULL;

CSingle * CSingle ::CreateInstance()

{

If(!m_pSingle)

m_pSingle = new Csingle;

      return m_pSingle;

}

CSingle :: DeleteInstance ()

{

delete m_pSingle;

:m_pSingle = NULL;

}

 

      它能够实现“不让其它类继承”,但很显然这种方式不好,它只能有一个实例。那么下面这样呢?

class CExample

{

public:

      CExample* CreateInstance()

{

return new CExample;

}

private:

      CExample();

CExample(CExample&);

};

      

      网上很多人提到这种方法。我个人也认为这个方法可行。但是这不是最理想的方法,因为这种方法限制了CExample对象的创建:new CExample 和CExample example。

 

B)http://springsun.bokee.com/218785.html上提供了下面这种方法。
// Usable.h
class Usable;

class Usable_lock

{

    friend class Usable;

private:

    Usable_lock() {}

    Usable_lock(const Usable_lock&) {}

};

       

class Usable : public virtual Usable_lock

{

// ...

public:

    Usable();

    Usable(char*);//这是为什么没有明白。

    // ...

};

       

Usable a;

class DD : public Usable { };

DD dd;  // error: DD::DD() cannot access

           // Usable_lock::Usable_lock(): private member

    //(参见《The Design and Evolution of C++》,11.4.3节) 
 

这跟上面一开始给出的代码原理是一样的。只不过上面将其模板化了。我个人推荐这里给出的方法。

      下面的讨论,如果你看最上面的代码费劲,看这里的代码好了。我也将以这里的代码为例进行说明。

 

为什么需要friend class

      

当创建Usable对象时需要调用Usable_lock的构造函数,而Usable_lock的构造函数是私有的,所以friend class Usable;是必要的。

 

虚拟继承

      

说老实话,我以前做项目没有使用过这个东西。这次正好有机会,仔细研究了一下。

从《C++程序设计语言(特别版)》和《c++_primer》中可以知道虚拟继承是一项专为解决多继承中的一个问题的。看下面的代码:

//非虚拟继承

class Base

{

public:

      Base()

      {

             cout<<"Base construct"<<endl;

      }

};

 

class CA:public Base

{

public:

      CA()

      {

             cout<<"CA construct"<<endl;

      }

};

 

class CB:public Base

{

public:

      CB()

      {

             cout<<"CB construct"<<endl;

      }

};

 

class CC:public CA,public CB

{

public:

      CC()

      {

             cout<<"CC construct"<<endl;

      }

};

 

int main()

{

      CC tt;

      getchar();

      return 0;

}

//虚拟继承

class Base

{

public:

      Base()

      {

             cout<<"Base construct"<<endl;

      }

};

 

class CA: virtualpublic Base

{

public:

      CA()

      {

             cout<<"CA construct"<<endl;

      }

};

 

class CB: virtualpublic Base

{

public:

      CB()

      {

             cout<<"CB construct"<<endl;

      }

};

 

class CC:public CA,public CB

{

public:

      CC()

      {

             cout<<"CC construct"<<endl;

      }

};

 

int main()

{

      CC tt;

      getchar();

      return 0;

}

运行结果如下:

Base construct

CA construct

Base construct

CB construct

CC construct

运行结果如下:

Base construct

CA construct

CB construct

CC construct

 

 

后面一段程序使用了虚拟继承,运行时少调了一次Base construct。为什么呢?

四个类的继承关系如下:

                                           

没有虚拟继承的情况下,多重继承下的对象可能的布局:

                              

                              

                                               CC对象有两个Base子对象。

 在虚拟继承的情况下,多重继承下的对象的可能布局:

                                                              

                     CA和CB子对象只保留了一个对Base子对象的引用. 

(这两幅图参考自《C++程序设计陷阱》)

  

   也就是说:在虚拟继承的情况下由最底层的派生类的构造函数初始化虚基类。

  

   在非虚拟继承中,子类只能初始化它的直接父类,而在虚拟继承中最底层的子类可以初始化虚基类。比如:

class Base

{

public:

      Base(char * pName = "Base")

      {

             strName = pName;

             cout<<"Base construct"<<endl;

             cout<<strName.data()<<endl;

      }

private:

      string strName;

};

 

class CA:virtual public Base

{

public:

      CA():Base("CA")

      {

             cout<<"CA construct"<<endl;

      }

};

 

class CB:virtual public Base

{

public:

      CB():Base("CB")

      {

             cout<<"CB construct"<<endl;

      }

};

 

class CC:public CA,public CB

{

public:

      CC():Base("CC")

      {

             cout<<"CC construct"<<endl;

      }

};

 

我们可以这样理解:在非虚拟继承中,创建孙子类对象时,孙类对象先构造它的父类,父类又先构造父类的父类,这样一层一层向上追溯;在虚拟继承中,孙子类对象是直接调用了虚基类的构造函数,再处理非虚拟父类的(详细解释可以参考《C++程序设计陷阱》)。

      

到这里我们就能解释为什么上面提到的http://springsun.bokee.com/218785.html中的代码要使用虚拟继承了。如果不使用虚拟继承,那么class DD构造时就先调用class Usable_lock的构造函数。因为class Usable_lock是class Usable的友元类,所以它能够调用class Usable的构造函数了。这样就能实现class DD继承于class Usable_lock了。如果使用的是虚拟继承,那么class DD构造时就先调用class Usable的构造函数,显然这是不行的。因为class Usable的构造函数是私有的,而class DD又不是它的友元类。这样编译器就会提示不能访问class Usable的私有构造函数了。

      到这里我们也就能理解最开始给出的那个模板类了。

 

NoInherit_中friend T怎么理解

      我们象下面这样来使用这个模板:

class CTest :publicNoInherit< CTest >

{

      //……

}

   这显然是行得通的。因为有了friend T,CTest就成了NoInheritNoInherit_的友元,从而能够初始化这两个类。

      如果我们这样:

class CMyClass:public CTest      //error

{

}

就会出错。因为创建CMyClass对象时不能够造NoInheritNoInherit_。从而CTest就是一个不能被人继承的类了。我们自己的工作就在CmyClass中完成。

关于模板特化:

  http://blog.csdn.net/SmartPtr/archive/2007/06/10/1647144.aspx

0 0
原创粉丝点击