程序员面试题精选100题(32)-不能被继承的类

题目:用C++设计一个不能被继承的类。

分析:这是Adobe公司2007年校园招聘的最新笔试题。这道题除了考察应聘者的C++基本功底外,还能考察反应能力,是一道很好的题目。

Java中定义了关键字final,被final修饰的类不能被继承。但在C++中没有final这个关键字,要实现这个要求还是需要花费一些精力。

首先想到的是在C++ 中,子类的构造函数会自动调用父类的构造函数。同样,子类的析构函数也会自动调用父类的析构函数。要想一个类不能被继承,我们只要把它的构造函数和析构函数都定义为私有函数。那么当一个类试图从它那继承的时候,必然会由于试图调用构造函数、析构函数而导致编译错误。

可是这个类的构造函数和析构函数都是私有函数了,我们怎样才能得到该类的实例呢?这难不倒我们,我们可以通过定义静态来创建和释放类的实例。基于这个思路,我们可以写出如下的代码:

///////////////////////////////////////////////////////////////////////

// Define a class which can't be derived from

///////////////////////////////////////////////////////////////////////

class FinalClass1

{

public:

      static FinalClass1GetInstance()

      {

            return new FinalClass1;

      }

 

      static void DeleteInstanceFinalClass1pInstance)

      {

            delete pInstance;

            pInstance = 0;

      }

 

private:

      FinalClass1() {}

      ~FinalClass1() {}

};

这个类是不能被继承,但在总觉得它和一般的类有些不一样,使用起来也有点不方便。比如,我们只能得到位于堆上的实例,而得不到位于栈上实例。

能不能实现一个和一般类除了不能被继承之外其他用法都一样的类呢?办法总是有的,不过需要一些技巧。请看如下代码:

///////////////////////////////////////////////////////////////////////

// Define a class which can't be derived from

///////////////////////////////////////////////////////////////////////

template <typename Tclass MakeFinal

{

      friend T;

 

private:

      MakeFinal() {}

      ~MakeFinal() {}

};

 

class FinalClass2 : virtual public MakeFinal<FinalClass2> (这里virtual继承很重要)

{

public:

      FinalClass2() {}

      ~FinalClass2() {}

};

这个类使用起来和一般的类没有区别,可以在栈上、也可以在堆上创建实例。尽管类MakeFinal<FinalClass2>的构造函数和析构函数都是私有的,但由于类FinalClass2是它的友元函数,因此在FinalClass2中调用MakeFinal<FinalClass2>的构造函数和析构函数都不会造成编译错误。

但当我们试图从FinalClass2继承一个类并创建它的实例时,却不同通过编译。

class Try : public FinalClass2

{

public:

      Try() {}

      ~Try() {}

};

 

Try temp;

由于类FinalClass2是从类MakeFinal<FinalClass2>虚继承过来的,在调用Try的构造函数的时候,会直接跳过FinalClass2而直接调用MakeFinal<FinalClass2>的构造函数。非常遗憾的是,Try不是MakeFinal<FinalClass2>的友元,因此不能调用其私有的构造函数。

基于上面的分析,试图从FinalClass2继承的类,一旦实例化,都会导致编译错误,因此是FinalClass2不能被继承。这就满足了我们设计要求。

问题:

第二个方案使用友元类是个不错的方法。只是我有点不明白,为什么要用virtual继承呢??FinalClass2已经是个不能继承的类了,那么为什么还要担心出现那种菱形继承的情况呢?
 
调用try的构造函数时,会先调用它包含的所有virtual base类的构造函数,然后再调用它上层的base类构造函数,然后是设置vptr,最后是初始化列表和子类构造函数体内的用户代码。try不能调用MakeFinal的私有成员,因此引发编译错误。
 
如果不是virtual继承,那么try首先调用的是它上层base类的构造函数,也就是FinalClass的构造函数,然后由FinalClass的构造函数来调用MakeFinal的构造函数,由于FinalClass是MakeFinal的友元,因此该调用合法,所以try得以正确构造,而没有编译错误。
 

虚基类只会创建虚基类的一个对象。 (只调用一次基类的构造函数)。虚基类的构造函数由继承层次最深的类调用(if a class inherits one or more classes that have virtual parents, the most derived class is responsible for constructing the virtual base class.)

如果要识别某类的父类为虚基类,该派生类的继承列表前必须添加virtual关键字。否则将不识别该类的父类为虚基类

比如下例中若类A或类B中有一个未加virtual关键字,仍将调用基类构造函数两次

参考;http://zhedahht.blog.163.com/blog/static/25411174200793181548842/

 http://www.cnblogs.com/youxin/archive/2013/03/15/2961219.html

原文地址:https://www.cnblogs.com/youxin/p/3349870.html