创建不能被继承的类?只在栈上?只在堆上?

来源:互联网 发布:ubuntu启动顺序 编辑:程序博客网 时间:2024/05/20 20:44

【不被继承】

首先想到的是在C++中,子类的构造函数会自动调用父类的构造函数。同样,子类的析构函数也会自动调用父类的析构函数。要想一个类不能被继承,只要把它的构造函数和析构函数都定义为私有函数。那么当一个类试图从它那继承的时候,必然会由于试图调用构造函数、析构函数而导致编译错误。

可是这个类的构造函数和析构函数都是私有函数了,怎样才能得到该类的实例呢?可以通过定义静态来创建和释放类的实例。基于这个思路,可以写出如下的代码:

///////////////////////////////////////////////////////////////////////class FinalClass1{public :      static FinalClass1* GetInstance()      {            return new FinalClass1;      }       static void DeleteInstance( FinalClass1* pInstance)      {            delete pInstance;            pInstance = 0;      } private :      FinalClass1() {}      ~FinalClass1() {}};

这个类是不能被继承,但在总觉得它和一般的类有些不一样,使用起来也有点不方便。比如,只能得到位于堆上的实例,而得不到位于栈上实例。

那有没有既能让它不被继承,除此之外可以和其他类一样的方法呢?

#include <iostream>using namespace std;template <typename T>class Base{    friend T;private:    Base(){        cout << "base" << endl;    }    ~Base(){}};class B:virtual public Base<B>{   //一定注意 必须是虚继承public:    B(){        cout << "B" << endl;    }};class C:public B{public:    C(){}     //继承时报错,无法通过编译};int main(){    B b;      //B类无法被继承    //C c;    return 0;}

类Base的构造函数和析构函数因为是私有的,只有Base类的友元可以访问,B类在继承时将模板的参数设置为了B类,所以构造B类对象时们可以直接访问父类(Base)的构造函数。

为什么必须是虚继承(virtual)呢?

参见 c++Primer 4th 第17.3.7节 特殊的初始化语义

     通常每个类只初始化自己的直接基类,但是在虚继承的时候这个情况发生了变化,可能导致虚基类被多次初始化,这显然不是我们想要的。(例2: AA,AB都是类A的派生类,然后类C又继承自AA和AB,如果按之前的方法会导致C里面A被初始化两次,也会存在两份数据)

    为了解决重复初始化的问题,从具有虚基类的类继承的类在初始化时进行了特殊处理,在虚派生中,由最低层次的派生类的构造函数初始化虚基类。在我们上面的例1中就是由C的构造函数控制如何进行虚基类的初始化。

为什么B类不能被继承?

   回到例1,因为B是Base的友元,所以B对象可以正常创建,但由于B使用了虚继承,所以如果要创建C对象,那么C类的构造函数就要负责虚基类(Base)的构造,但是Base的构造函数是私有的,C没有访问的权限(ps:友元关系不能被继承的),所以例1中的C类在编译时就会报错。这样B类就不能被继承了。


【只在堆/栈上创建对象】

C++中,对象的建立分为两种,一种是静态建立,如A a;另一种是动态建立,如 A *ptr=new A;

静态建立:由编译器为对象在栈上分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象,使用这种方法,直接调用类的构造函数

动态建立:是通过new运算符将对象建立在堆空间中,这个过程分为两步,第一步是执行operator new()函数,在堆空间中搜索合适的内存并进行分配,第二步是调用构造函数构造对象,初始化这片内存空间,这种方法,间接调用类的构造函数

 

接下来进行限制对象只能在那里建立?

1.只能建立在堆上?

 就是不能静态的建立对象,即不能直接调用类的构造函数。

分析:

(1)我们容易想到将构造函数设为私有,在构造函数私有之后,无法在类外部调用构造函数来构造类对象,只能使用new运算符来建立对象,然而,前面已经说过,new运算符的执行过程分为两步,c++提供new运算符的重载,其实是只提供operator new() 函数,而operator()函数是进行内存分配的,无法提供构造功能,因此这种方法是不可以的

(2)对象建立在栈上面时,是由编译器分配空间的,调用构造函数来构造栈对象,当对象使用完之后,编译器会调用析构函数来释放栈对象所占的空间,编译器管理了对象的整个生命周期,编译器为对象分配空间的时候,只要是非静态的函数都会检查,包括析构函数,但是此时析构函数不可访问,编译器无法调用类的析构函数来释放内存,那么编译器将无法在栈上为对象分配内存

复制代码
class a{    public :        a(){}        void destory(){            delete this;        }    private:        ~a(){};}; 
复制代码

   那可以尝试使用a  b;来建立对象,编译报错,提示析构函数无法访问,这样就只能使用new来创建对象,构造函数是共有的,可以直接调用,但是类中必须通过destory()函数来进行内存的释放,类对象使用完之后,必须调用destory()函数

   上述方法两个缺点:(1)无法解决继承问题,因为通常情况之下a作为基类,一般析构函数要设为vitual,然后子类重写,已实现多态,因此析构函数不能设为private,不过c++还有protected访问控制方式,将析构函数设置为protected,这样子类可以访问,但是类外无法访问。(2)使用不方便,不统一,因为你使用了new创造了对象,但是不能使用delete释放对象,必须使用destory函数,这种方式比较怪异,所以我们也可以将构造函数设置为protected,同时提供另一public static create()函数来进行替代new。这样  create()创建对象在堆上, destory()释放内存

复制代码
class a{    public :        static a* create(){            return new a();        }        void destory(){            delete this;        }    protected:        a(){};        ~a(){};}; 
文中刚开始的布恩那个被继承的类也只能在堆上创建对象。

 

2.只能建立在栈上

   只有使用new操作符,才会使对象建立在堆上,因此只要禁用new操作符就可以了,所以我们将operator new()操作符设为保护或者私有就可以了

复制代码
class a{    public :        a(){};        ~a(){};    protected:        void* operator new(size_t t){};  //重载new,注意参数以及返回值         void operator delete(void *ptr){};  //重载了new,就要重载delete         }; 
阅读全文
0 0
原创粉丝点击