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的构造过程是这样的:
- 因为C直接继承自B,所以C首先需要执行B的构造函数,因为B的构造函数对C而言是public的,所以这一步不会出错;
- 在执行B的构造函数的时候,因为B继承自A,所以会在B构造的过程中调用A的构造函数,此时B私有继承自A,A的构造函数在B中是private的,但是B类里是可以访问到的,所以在构造B的时候也不会出错。
所以上面那种处理方法是不能让B成为不能被继承的类的,子类C仍然可以正常构造!!!
如果把上面B私有继承替换成虚拟的私有继承呢?
此时看看构造C的过程:
- 在这个继承体系中存在了虚基类,所以首先应该调用A的构造函数,因为是跳过了B直接调用A,所以此时A的构造函数是public,这一步不会出错;
- 然后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.使用友元和模板
上面的解法总有一种怪怪的感觉,其实在第一错误的解法上稍微做一点改进就有一种很漂亮的解法了。
仍然是上面那个继承体系:
- 将A的构造函数和析构函数都声明为private的,但是将B作为A的友元类,这样B就可以访问A的构造函数和析构函数了,此时B能正常构造;
- 为了使B的子类C不能被正常构造,可以让C直接调用A的构造函数,那么将B设置成虚拟继承自A;
- 因为友元关系是不能被继承的,所以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; }
这就是最终的一种很好的写法了。
版权声明:本文为博主原创文章,未经博主允许不得转载。