一、设计一个类不能被继承
方法1:
利用静态成员函数。。
由于一个类不能被继承,也就是该类的构造函数不能被外界访问到,那么就需要将该类的构造和析构函数声明为私有的,
而要调用一个类的任意一个非静态成员变量或者函数都需要对象来调用(需要this指针),但是我们在外界又不能直接创建该类的对象,
所以就想到了静态成员变量和函数的优点,不需对象也可以调用,所以我们可以定义一个静态成员函数,在该静态函数内部完成对象的创建,
然后将创建的对象返回,这样我们在类外部可以通过类的作用域限定符来调用该类的静态成员函数来获得该类的对象。
但是这种方法有个缺陷:创建的对象都是在堆上创建的。不能再栈上创建对象。
代码如下:
//设计一个类不能被继承
class sealedClass1
{
public:
static sealedClass1* Getinst()
{
return new sealedClass1();
}
static void destory(sealedClass1* p)
{
if (p)
{
delete p;
p = NULL;
}
}
private:
int _a;
sealedClass1()
:_a(0)
{}
~sealedClass1()
{}
};
方法2.:
能不能设计一个和一般类在功能上没有差别而不能被继承的类呢?
引用:虚继承
需要一个辅助类(Tmp),将其构造函数和析构函数都声明为私有的,然后再设计一个不能被继承的类sealedClass ,(该类虚继承这个辅助的类),并且该类还必须是这个辅助类的友元类,这样的话该类才可以调用辅助类的构造函数。
现在假设我们设计的这个类sealedClass 可以被类C继承,由于sealedClass 类是虚继承Tmp类的,那么类C就必须能够调用tmp的构造函数,这可以从类C的对象模型中得出,而又因为类C不是Tmp类的友元类,所以C类不能直接调用Tmp类的构造函数,所以使得类sealedClass不能被继承。
class sealedClass;
class Tmp
{
public:
friend sealedClass;
private:
Tmp()
:_a(0)
{}
~Tmp()
{}
int _a;
};
class sealedClass : virtual public Tmp
{
public:
sealedClass()
:_b(1)
{}
~sealedClass()
{}
private:
int _b;
};
class C :public sealedClass
{
public:
C()
:_c(3)//出现编译错误
{}
private:
int _c;
};
在C++中,类的对象建立分为两种:
一种是静态建立:如A a;
另一种是动态建立:如A* ptr=new A;
这两种方式是有区别的。
静态建立一个类对象,是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。使用这种方法,直接调用类的构造函数。
动态建立类对象,是使用new运算符将对象建立在堆空间中。这个过程分为两步:
第一步是执行operator new()函数,在堆空间中搜索合适的内存并进行分配;
第二步是调用构造函数构造对象,初始化这片内存空间。这种方法,间接调用类的构造函数。
二、设计一个类只能在堆上创建对象
思路:
我们容易想到将构造函数设为私有。在将构造函数设为私有之后,就无法在类外部调用构造函数来构造类对象,只能使用new运算符来建立对象。
然而,new运算符的执行过程分为两步:
①调用operator new()运算符在堆上为对象开辟空间;
②会调用对象的构造函数使其在堆上为对象开辟出来的空间处构造一个对象。
很显然,第②步还是要调用对象的构造函数,所以这种方法不可以。
当对象建立在栈上面时,是由编译器来分配内存空间的,调用构造函数来构造栈对象。当对象的生命周期快结束时,编译器会调用析构函数来释放栈对象所占的空间。
编译器管理了对象的整个生命周期。如果编译器无法调用类的析构函数,情况会是怎样的呢?比如,类的析构函数是私有的,编译器无法调用析构函数来释放内存。
所以,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。
因此,将析构函数设为公有的,类对象就无法建立在栈上了。
class A
{
public:
A(int x = 0)
:_a(x)
{}
void destory()
{
delete this;
}
private:
~A()
{}
int _a;
};
当在栈上创建对象时(A a;),编译器报错,析构函数在类外不可访问。
但是有个问题,这样看起来很奇怪,用new创建的对象,没有用delete来释放,如果这里用delete来释放对象时,就会发现根本不能成功,原因是delete释放对象空间时也做了两件事:
①调用类的析构函数清理对象空间;
②调用operator delete()来释放对象的空间。
由于析构函数为保护的,在类外不能访问,所以不能使用这种方法释放对象的空间。
所以我们就想到可以调用类的一个接口函数在堆上new出对象,再调用一个接口函数去释放对象的空间。
我们都知道,调用类中的非静态成员函数需要类对象,所以需要将接口函数声明为静态的。
class A
{
public:
static A* Getinst()
{
return new A();
}
void destory()
{
delete this;
}
protected:
A(int x = 0)
:_a(x)
{}
~A()
{}
int _a;
}
三、 设计一个类只能在栈上创建对象
由上题的分析,我们知道,要使得创建一个对象是只能在栈上创建,也就是不能在堆上创建对象,即就是不能使用new操作符在在堆空间来new一个对象,那只要禁用new操作符就可以了,这就好办了,重载一个operator new(),设定该重载函数时私有的。
class A
{
public:
A(int x = 0)
:_a(x)
{}
~A()
{}
private:
void* operator new(size_t N)
{
return NULL;
}
void operator delete(void* p)
{}
int _a;
};
小结:
1.设计不能被继承的类,需要考虑到对象模型;
2.只能在栈上创建对象,将析构函数的访问限定符设为私有的或者保护的;
3.只能在堆上创建对象,使得在类外部不能使用new操作符在对堆上开辟空间。