面试题48 : 不能被继承的类
题目:用C++设计一个不能被继承的类
C#中定义了sealed,被sealed修饰的类不能被继承
Java中被final修饰的类不能被继承。
C++中没有类似的定义,需要自己实现。
方案一:
在C++中,子类的构造函数会自动调用父类的构造函数。同样,子类的析构函数也会自动调用父类的析构函数。要想一个类不能被继承,只要把他的析构函数和构造函数都定义为私有函数,那么,当一个类试图去继承他的时候,必然会由于试图调用析构函数、构造函数而导致编译错误。
当一个类的构造函数和析构函数都定义为私有的之后,就无法得到这个类的实例,故还需要在类中额外定义一个静态函数来创建和释放该类的实例。
///
// Define a class which can't be derived from
///
class FinalClass1
{
public:
static FinalClass1* GetInstance()
{
return new FinalClass1();
}
static void DeleteInstance( FinalClass1* pInstance)
{
delete pInstance;
pInstance = 0;
}
private:
FinalClass1() {}
~FinalClass1() {}
};
使用这个类,只能值得类在堆上的实例,而得不到在栈上的实例。
方案二:
利用虚继承
template <typename T> class MakeFinal
{
friend T;
private:
MakeFinal() {}
~MakeFinal() {}
};
class FinalClass2 : virtual public MakeFinal<FinalClass2>
{
public:
FinalClass2() {}
~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不能被继承。这就满足了我们设计要求。
注:方法二可移植性不好,虽然sealedClass2在virtual studio 中能够编译,但由于GCC中对friend的要求不同于virtual studio,
目前在最新的GCC中还不支持模板参数类型作为友元类型。