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


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;

}
这就是最终的一种很好的写法了。



阅读更多

没有更多推荐了,返回首页