C++中定义一个不能被继承的类

来源:互联网 发布:俄罗斯进出口贸易数据 编辑:程序博客网 时间:2024/05/24 02:28


1.一种错误的解法


最开始是从构造函数开始着手(先声明这种方法不能定义一个不能被继承的类,这是一种错误的方法,但是很容易往这方面想),假设存在下面的继承体系:

现在假设B是一个不能被继承的类,那么如果存在B的子类C,那么C的构造过程应该会报错,那么如何能够让B能正常构造而C不能正常构造呢?首先A,B,C的构造函数和析构函数都假设是public的,最开始想的是让B私有继承自A,根据private继承的特性,父类中public和protected的成员在子类中都会变成private的,那么A的构造函数在B中就变成private的了,然后C在继承自B时是无法访问B中的private成员的,这样C就无法调用A的构造函数了。。。。开始这样想的,但是这种想法存在一个很大的问题。就是如果是普通的函数,前面的想法是正确的,但是对于构造函数而言就不能这么思考,对于上面的继承体系。C的构造过程是这样的:

  1. 因为C直接继承自B,所以C首先需要执行B的构造函数,因为B的构造函数对C而言是public的,所以这一步不会出错;
  2. 在执行B的构造函数的时候,因为B继承自A,所以会在B构造的过程中调用A的构造函数,此时B私有继承自A,A的构造函数在B中是private的,但是B类里是可以访问到的,所以在构造B的时候也不会出错。

所以上面那种处理方法是不能让B成为不能被继承的类的,子类C仍然可以正常构造!!!

如果把上面B私有继承替换成虚拟的私有继承呢?

此时看看构造C的过程:

  1. 在这个继承体系中存在了虚基类,所以首先应该调用A的构造函数,因为是跳过了B直接调用A,所以此时A的构造函数是public,这一步不会出错;
  2. 然后C调用B的构造函数,能正常调用

从上面的分析可以看出使不使用虚基类效果是一样的。

#include <iostream>#include <string>using namespace std;class A{public:int a;A(){cout<<"a"<<endl;}};//这里使不使用虚基类效果是一样的class B:private  virtual A{};class C:public B{};int main(){A a;B b;C c;cout<<"success"<<endl;return 0;}




2.使用静态函数来实现


上面的方法虽然是错误的,但是它也提供了一种思路,只是有些地方没有处理好。定义不能被继承的累关键仍然是要从构造函数着手。

在C++中要定义一个不能被继承的类,可以这么思考,如果存在子类,那么子类会调用父类的构造函数,那么我们可以将这个类的构造函数和析构函数都声明是私有的,那么这样它的子类构造时就会报错,这样这个类就是不能被继承的了。如果我们要获取这个类的对象,可以通过一个静态的方法来获取,这个静态 方法可以获取这个类的对象,也可以获取这个类的对象的指针,对应于在栈上还是在堆上分配内存。


2.1堆中的实现


先看在堆上分配内存的情况:

#include <iostream>using namespace std;class A{//构造函数和7构函数都被声明是私有的,这样就不能被继承了private:A(){cout<<"a con"<<endl;}~A(){cout<<"a des"<<endl;}//提供两个公有的方法来获取和释放A类型的对象public:static A* getA(){A *a=new A();return a;}static void deleteA(A* a){delete a;}};class B:public A{};int main(){A* a=A::getA();A::deleteA(a);//B b;return 0;}
上面的代码能正常运行:但是注意这种方法构造的A对象都位于堆内存中,并且一定要注意通过定义的A::getA()来获取对象,A::delete(a)来释放对象,不能通过delete a来释放对象,因为此时析构函数是私有的了。


可以发现A的构造函数和析构函数都正常执行了,如果把main函数中注释的哪行代码注释掉,结果就会报错:一直提示A的构造函数是私有的。




2.2栈中的实现


上面这种方式已经可以定义一个不能被继承的类,但是对象A始终存在堆内存中,于是我想能不能尝试在栈内存中构造A的对象?

在栈上构造对象理论上讲是只需要在静态函数getA中返回一个对象A就可以了,不需要返回一个指向A的指针,如下。但是这里构造A是没问题了,可是析构A时出问题了,因为在getA返回对象a时存在一个临时对象,这个临时对象需要析构,然后main函数结束时也有一个对象需要析构,所以析构时会提示错误。

#include <iostream>using namespace std;class A{private:A(){cout<<"a con"<<endl;}//public:~A(){cout<<"a desc"<<endl;}//提供两个公有的方法来获取和释放A类型的对象public:static A getA(){A a;return a;}};class B:public A{};int main(){A a=A::getA();B b;return 0;}


出错提示:


这个错误提示和上面的分析是一样的,即在调用A的析构函数时出错,如果把上面的析构函数声明是public(即把上面的析构函数上面的对public的注释去掉),构造函数声明是private的,那么就能正常构造A对象了。并且不能定义A的子类了。但是强烈不建议这么这种做法,因为一般构造函数和析构函数的访问修饰符是一样的!!!!


3.使用友元和模板


上面的解法总有一种怪怪的感觉,其实在第一错误的解法上稍微做一点改进就有一种很漂亮的解法了。

仍然是上面那个继承体系:

  1. 将A的构造函数和析构函数都声明为private的,但是将B作为A的友元类,这样B就可以访问A的构造函数和析构函数了,此时B能正常构造;
  2. 为了使B的子类C不能被正常构造,可以让C直接调用A的构造函数,那么将B设置成虚拟继承自A;
  3. 因为友元关系是不能被继承的,所以C调用A的构造函数时会报错

和第一种错误的解法相比,主要使将A的构造函数和析构函数声明是private的了,并且将B声明是A的友元类,其实这种解法和A的思路是一样的,就是让B能调用A的构造函数,让B的子类不能调用A的构造函数,只是第一种错误的解法没有满足这个要求。

更进一步,可以将它写成模板:

#include <iostream>using namespace std;template<class T>class A{friend T;//注意这里不需要class关键字//将构造函数和7构函数都声明是私有的private :A(){}~A(){}};class B:public virtual  A<B> //这里一定需要使用虚继承,只有使用虚继承,它的子类才会直接调用A类的构造函数,这样才会报错,如果是普通继承,那么通过B类调用A类的构造函数时时不会报错的{};class C:public B{};int main(){B b;cout<<"success"<<endl;//C c;return 0;}
这就是最终的一种很好的写法了。



2 0
原创粉丝点击