如何使类不能被继承zz

如果大家熟悉java的话应该知道java中有一种类不能被继承,那就是final类.这种类有很多用处,尤其是在大的项目中控制类的继承层次. 使子类数量不至于爆炸.在使用了多继承的类层次中这也是防止出现菱形继承层次结构的一个好办法. 要实现一个不能被继承的类有很多方法.

主要的思路就是使子类不能构造父类的部分,这样子类就没有办法实例化整个子类.这样就限制了子类的继承. 所以我们可以将父类的构造函数声明成为私有的,但是这样父类不就不能实例化.我想可以添加一个静态帮助函数来进行构造. 虽然这样很简陋.但是这的确是一种解决方法.

可是如果只有这个方法能够解决,那么C++实在是太不灵活了.而且这也不值得写一片文章出来!有没有办法解决上面的方法中的那些问题呢?

我们可以利用友员不能被继承的特性!

首先假设已经有一个类CXX.这是某一个类层次的分支,我们现在要从CXX继承一个Final子类CParent来,也就是CParent不能够被继承. 我们可以充分利用友员不能被继承的特点,也就是说让CParent是某一个类的友员和子类,CParent可以构造,但是CParent的子类CChild确不能继承那个友员特性,所以不能被构造.所以我们引入一个CFinalClassMixin.

我们对这个类的功能是这么期望的:

  • 任何类从它继承都不能被实例化
  • 同时这个类本身我们也不希望它被实例化.

如何实现这个类那?很简单!那就是实现一个构造函数和析构函数都是private的类就行了.同时在这类里面将我们的CParent声明为友员. 代码如下:


class CFinalClassMixin
{
friend class CParent;
private:
	CFinalClassMixin(){}
	~CFinalClassMixin(){}
};

>//我们的CParent代码应该如下: class CParent:publicCXXX { public: CParent(){} ~CParent(){} };

它是从CXXX扩展的一个类(注,此时它还是能够被继承).现在我们需要它不能被继承.那么只要将代码改成


class CParent:public CFinalClassMixin, public CXXX
{
public:
CParent(){}
~>CParent(){}
};

就行了.现在从CParent继承一个子类试试

class CChild:public CParent{};

编译一下代码试试,发现:竟然没有作用!!

现在再回想一下我们这么操作的原因,也就是这个方案的原理,那就是让父类可以访问Mixin类的构造函数,但是子类不能访问.

现在看看我们的代码,发现父类是CFinalClassMixin类的友员,可以访问它的构造函数.因为友员不能继承,所以CChild不能访问CFinalClassMixin的构造函数.所以应该不能被实例化.

CChild的确不能访问CFinalClassMixin的构造函数,但是它却不必调用它!我想这就是问题的原因所在.CChild是通过CParent来构造CFinalClassMixin的,所以这个友员对他并没有什么用处!

现在问题找到了.要解决很简单.只要让CChild必须调用CFinalClassMixin的构造函数就行了,怎么才能达到目的呢?

还记得虚继承吗?虚继承的一个特征就是虚基类的构造函数由最终子类负责构造!所以将CParent从CFinalClassMixin继承改成从CFinalClassMixin虚继承就可以了.代码如下:


class CParent:virtual public CFinalClassMixin, public CXXX
{
public:
	CParent(){}
	CParent(){}
};

现在试试,行了.

但是可能有些人会对多继承心有余悸!但是我们这里并没有必要这么担心!为什么?因为我们的CFinalClassMixin类是纯的!pure! 也就是说它根本没有成员变量!那么我们就根本不用担心多继承带来的最大问题.菱形继承产生的数据冗余.以及二义性.

现在还有个不足!那就是我们不能每次使用这个CFinalClassMixin类就在里面加上对某个类的友员声明啊!这多麻烦啊! 虽然不是什么大问题,但是我觉的还是要解决,因为我充分信任C++!

解决的方法也很简单!那就是使用模板!具体描述就省略了,给出代码大家一看就知道了

下面是我得测试程序的完整代码(其中的CFinalClassmixin已经改成模板)


// finaltest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
using namespace std;

template<class T>

class CFinalClassMixin

{

friend T;

private:

CFinalClassMixin(){}

~CFinalClassMixin(){}

};

class CXXX

{

public:

CXXX(){cout << "I am CXXX" << endl;}

~CXXX(){}

};

class CParent:virtual public CFinalClassMixin<CParent>, public CXXX

{

public:

CParent(){}

~CParent(){}

};

class CChild:public CParent{};

int main(int argc, char* argv[])

{

CParent a; // 可以构造

//CChild b; //不能构造

return 0; }

现在只要对不想被继承的类加入一个CFinalClassMixin混合类做父类就行了.

通过限制构造函数,我们就达到了限制继承的目的.但是这对有些还是个例外,比如全是静态函数的类.这些类本身就不需要构造. 所以我们对它没有办法.但是在大多数情况下,一个全是静态函数的类多少暗示了程序本身的设计可能是需要斟酌的.

其实这只是Mixin类(混合类)使用的一个小小例子.还有很多其他的用处,比如UnCopiale等等.就不多说了. 我想说明的是大家可能对多继承比较反感.但是过分否定也是得不偿失的.现在对多继承到底应不应该使用还处在争论阶段. 我觉得一个方法是否使用得当,关键还是在于使用的人.

lythm@citiznet

2002 05 27


相关文章
对该文的评论
ajoo ( 2002-06-23)
关于lostinet(我总觉得你的id象是"lobster" ^_^)的方法, 我觉得实际工作中已经很够用了。
正常来说,任何人想扩展你写的类, 他都会来向你咨询一下, 或者, 至少是看一下你的文档吧?所以说, 虽然文档并不能enforce anything,但我想, 在实际开发中, 并没有多大问题。

或许这也是一个理由为什么C++不加入"final" keyword吧。“反正你也不大需要, 为什么还要加?"

Java中的"final", 其实除了工程上禁止别人subclass, 它实际上更主要是对编译器优化的一种支持。如果编译器知道这个类或者这个method是final的话, 它就可以不按虚函数的方式调用这个函数了。
C++倒是似乎缺乏这种对虚函数override的禁止:“我这就是这个函数的最终实现了,没有人可以override我。编译器你放心!”而"friend"的使用,似乎很难对编译器起任何类似的作用。




ajoo ( 2002-06-23)
I dont think typename, explicit are superfluous at all. Just as I don't think "final" keyword is superfluous.
with "final", you don't have to play this kind of trick. that can effectively make your code simpler.
and, better than "explicit", "final" will not potentially hurt any existing C++ feature, semantics and optimizability.
I guess the reason "final" is not in standard is because that the "standard" thing is quite political. I won't say anything added to the standard is necessarily the best approach (of course, it has to be good enough). anything not added to the standard is necessarily bad or useless.


about minimal keyword, your answer sounds so familiar. "communism is the best one". 
it really does not mean much. yes, we need to be conservative about adding keywords, but that does not necessarily disqualify adding a keyword, nor does it mean that the current collection of keywords is already perfect.

using debug/release trick should also be cautious. don't you see that the MI and virtual base class is changing the object's memory layout? if we allow such difference exist between release and debug, it's a sign of danger at least.
conditional compilation is one big reason why refactoring of C++ code is so hard!
And also, it is yet another step further toward code complexity.


prototype ( 2002-06-22)
> prototype: do you feel "explicit", "typename" are also superfluous?
no, i don't (but for different reasons). how about you? and why?

> "minimal keyword", don't know what that means. who do you compare to say it's minimal? to itself?
it is "minimal number of keywords". means to keep the number of keywords as less as possible.

> macro can remove overhead? never know macro can be that great. although I myself
> am a macro lover.
use the 'ndebug' trick. thus no overhead in the release version. -- thought that was obvious.
ajoo ( 2002-06-22)
关键字?
what do you mean?

prototype: do you feel "explicit", "typename" are also superfluous? (probably not, because they are already in C++)
Thank god C++ has a "friend" for you to do this trick. Some people still complain about "friend" violating encapsulation! They don't know "friend" can play such an important role. You never know what a keyword can be used in C++, right? Too many possibilities. don't think the C++ author knows "friend" can be used for this. :)


"minimal keyword", don't know what that means. who do you compare to say it's minimal? to itself?

clean trick? well, we may have different view of clean code. 
macro can remove overhead? never know macro can be that great. although I myself am a macro lover.


prototype ( 2002-06-21)
原文地址:https://www.cnblogs.com/dayouluo/p/198922.html