在C#中有sealed关键字,而Java中有final关键字,其作用就是为了提供一种机制使一个类不能被继承。当然,C++不能^_^,但是他能实现,下面来讨论一下吧。
Method1:最简单的想法就是使一个类得构造函数和析构函数成为私有函数,这样,子类的构造函数和析构函数就无法调用父类的构造函数和析构函数,也就难以构造或者析构父类对象,就可以了。当然,这样我们也无法构造对象,这个,我们可以采用静态方法来创建和释放类得实例。如下:
- class NoDerivedClass
- {
- public :
- static NoDerivedClass* CreateInstance()
- {
- return new NoDerivedClass;
- }
- static void DeleteInstance(NoDerivedClass* pInstance)
- {
- delete pInstance;
- pInstance = 0;
- }
- private :
- NoDerivedClass() {}
- ~NoDerivedClass() {}
- };
PS:
因为一个类的构造函数可以有很多,所以仅仅将构造函数私有是不行的,析构函数只能有一个,要私有。
这种方法只能产生堆上的实例,而且构造和析构也必须调用类的静态方法,跟平时的情况不同。
Method2:这个方法的要点有三,一是虚拟继承,也就是子类直接调用父类的构造方法,二是友元函数,因为友元关系不能被继承,三是模板方法,采用模板是为了使用方便,其实不使用也可以,但是会造成不必要的修改。直接看结果:
- template <typename T>
- class NoDerivedClassHelper
- {
- friend T;
- private :
- NoDerivedClassHelper() {};
- ~NoDerivedClassHelper () {};
- };
- class NoDerivedClass: virtual public NoDerivedClassHelper<NoDerivedClass> //※
- {
- public :
- NoDerivedClass () {}
- ~ NoDerivedClass () {}
- };
说一下原理:当我们创建一个类从NoDerivedClass继承时,因为NoDerivedClass是从NoDerivedClassHelper中虚拟继承而来,所以我们的类会直接调用NoDerivedClassHelper的构造方法,但是其为私有,而我们的类又不像NoDerivedClass一样是NoDerivedClassHelper的友元,所以无法调用,编译出错,无法继承。这种方法创建的NoDerivedClass类既可以在堆上,也可以在栈上创建实例。
PS:
当然,虽然我们禁止NoDerivedClass被别人继承,但是NoDerivedClass却应该可以拥有父类的,很简单,比如NoDerivedClass的父类是CFather,那么※这一行应该改为如下所示:
class NoDerivedClass: virtual public NoDerivedClassHelper < NoDerivedClass >, public CFather
虽然是多继承,但是NoDerivedClassHelper类中没有成员变量,应该不能掀起多大的风浪的,不必担心多继承带来的问题,比如菱形继承产生的数据冗余以及二义性等。
为了简便,我们可以使用宏的方式简化声明的过程:
#define FINAL_CLASS : public virtual NoDerivedClassHelper
那么定义为: class NoDerivedClass FINAL_CLASS
在这个例子中我们使用了模板,其实就是为了方便,如果不使用模板的话就是这个样子:
- class NoDerivedClassHelper
- {
- friend class NoDerivedClass;
- private:
- NoDerivedClassHelper(){};
- ~NoDerivedClassHelper(){};
- };
- class NoDerivedClass :public virtual NoDerivedClassHelper
- {
- public:
- NoDerivedClass(){};
- ~NoDerivedClass(){};
- };
当然,在这种情况下每次实现NoDerivedClass类都要更改NoDerivedClassHelper,造成了不必要的修改,所以模板就很方便了。
Method3:这种方法和第二种方法类似,不过不使用friend的方法。Method2和 Method3的共同点就是使用虚继承,但是Method2采用了friend关系不继承的特性,那么我们的Method3是如何做的呢?如下:
- class NoDerivedClassHelper
- {
- protected:
- NoDerivedClassHelper(int){}
- ~NoDerivedClassHelper(){}
- };
- class NoDerivedClass : public virtual NoDerivedClassHelper
- {
- public:
- NoDerivedClass() : NoDerivedClassHelper(0) {}
- ~NoDerivedClass(){}
- };
解释一下,如果我们声明一个类从NoDerivedClass继承,那么由于是虚继承,这个类要直接调用NoDerivedClassHelper的构造函数,当然要调用无参数的构造函数,因为我们已经声明了NoDerivedClassHelper(int){},所以NoDerivedClassHelper并没有产生默认的构造函数,那么就会产生找不到合适的构造函数的错误,导致无法继承。
虚继承的奥义,就是在虚继承出现的继承层次中,总是在构造非虚基类之前构造虚基类。就是爷爷,父亲和孙子,本来的构造关系是父亲调用爷爷的构造函数,而现在是孙子直接调用爷爷的构造函数,虽然构造关系始终是基类先要被构造,但是构造函数的调用关系已经穿越…
Method4:如果我们想隐含掉虚拟继承关系,也就是我们提供的NoDerivedClassHelper并不需要用户来虚继承就好了,因为用户可能会漏掉虚继承,或者用户对虚继承心有余悸,那么看看如下的实现吧:
- template<typename CDerive, typename CHelper>
- class CNoDerivedClassHelperBase
- {
- friend CDerive;
- friend CHelper;
- private:
- CNoDerivedClassHelperBase(){}
- ~CNoDerivedClassHelperBase(){}
- };
- template<typename CDerive>
- class NoDerivedClassHelper : virtual public CNoDerivedClassHelperBase<CDerive, NoDerivedClassHelper>
- {
- public:
- NoDerivedClassHelper(){}
- ~NoDerivedClassHelper(){}
- };
- class NoDerivedClass : public NoDerivedClassHelper<NoDerivedClass>
- {
- public:
- NoDerivedClass(){}
- ~NoDerivedClass(){}
- };
当然,我们最后要使用的类就是NoDerivedClass,也就是我们以后要创建”final”类的时候只要从NoDerivedClassHelper中直接继承就可以了并不需要使用虚拟继承,虚拟继承已经被我们封装到了内部,对用户透明。可见,如果没有template<typename CDerive> 模板的使用,到NoDerivedClassHelper这一层已经是”final”了,但是这里的技巧就是使用模板使友元关系又降低了一层,使NoDerivedClass也是CNoDerivedClassHelperBase的友元,也可以调用CNoDerivedClassHelperBase的私有构造和析构函数,但是由于CNoDerivedClassHelperBaseà NoDerivedClassHelperà NoDerivedClass的继承关系,以及虚继承的使用,直接构造CNoDerivedClassHelperBase还是必须的,导致NoDerivedClass的无法继承。
说了这么多怎么做了,现在来说说为啥这么做吧,也就是为什么要实现一个”final”类。
如果我们创建的一个类不含有virtual的析构函数,那么如果我们继承了它,就有可能导致资源泄漏。
class B { . . . }; class D : public B { . . . }; B* pB = new D; delete pB; // resource leak还有,如果我们创建的类不希望别人继承,但是什么声明啊,警告啊,帮助文档是没有用的,尤其你在提供一个封装库的类,用户不会时时想着你的告诫,那么咋办,就根本不能让用户可以用,而不是靠用户头脑中的清规戒律来使用,事实不能用,他肯定就不会用了。
好了,就到这吧。