如何让对象只在堆或者栈中分配空间ANDC++禁止一个类被继承
在开始之前先来分析一下C++中的new运算符和operator new之间的关联。
new:指我们在C++里通常用到的运算符,比如A* a = new A或者调用带参数的构造函数; 对于new来说,有new和::new之分,前者位于std。
operator new():它是一个函数,并不是运算符。对于operator new来说,分为全局重载和类重载,全局重载是void* ::operator new(size_t size),在类中重载形式 void* A::operator new(size_t size)。还要注意的是这里的operator new()完成的操作一般只是分配内存,事实上系统默认的全局::operator new(size_t size)也只是调用malloc分配内存,并且返回一个void*指针。而构造函数的调用(如果需要)是在new运算符中完成的。
operator new
can be called explicitly as a regular function, but in C++, new
is an operator with a very specific behavior: An expression with the new
operator, first calls function operator new
(i.e., this function) with the size of its type specifier as first argument, and if this is successful, it then automatically initializes or constructs the object (if needed). Finally, the expression evaluates as a pointer to the appropriate type.一般情况下,编写一个类,是可以在栈或者堆分配空间。但有些时候,你想编写一个只能在栈或者只能在堆上面分配空间的类。这能不能实现呢?仔细想想,其实也是可以滴。
在C++中,类的对象建立分为两种,一种是静态建立,如A a;另一种是动态建立,如A* ptr=new A;这两种方式是有区别的。
1、静态建立类对象:是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。使用这种方法,直接调用类的构造函数。
2、动态建立类对象,是使用new运算符将对象建立在堆空间中。这个过程分为两步,第一步是执行operator new()函数,在堆空间中搜索合适的内存并进行分配;第二步是调用构造函数构造对象,初始化这片内存空间。这种方法,间接调用类的构造函数。
1、只能在堆上分配类对象,就是不能静态建立类对象,即不能直接调用类的构造函数。
容易想到将构造函数设为私有。在构造函数私有之后,无法在类外部调用构造函数来构造类对象,好像只能使用new运算符来建立对象。然而,前面已经说过,new运算符的执行过程分为两步,C++提供new运算符的重载,其实是只允许重载operator new()函数,而operator new()函数只用于分配内存,无法提供构造功能,还得要在类外部调用构造函数。因此,这种方法不可以。
当对象建立在栈上面时,是由编译器分配内存空间的,调用构造函数来构造栈对象。当对象使用完后,编译器会调用析构函数来释放栈对象所占的空间。编译器管理了对象的整个生命周期。如果编译器无法调用类的析构函数,情况会是怎样的呢?比如,类的析构函数是私有的,编译器无法调用析构函数来释放内存。所以,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。因此,将析构函数设为私有,类对象就无法建立在栈上了。代码如下:
class A
{
public:
A(){}
void destory(){delete this;}
private:
~A(){}
};
试着使用A a;来建立对象,编译报错,提示析构函数无法访问。这样就只能使用new操作符来建立对象,构造函数是公有的,可以直接调用。类中必须提供一个destory函数,来进行内存空间的释放。类对象使用完成后,必须调用destory函数。如果没有这个函数的话,即使用new运算符建立的对象也是不能完成释放的,因为它不能使用析构函数。
上述方法的缺点:
一、无法解决继承问题。如果A作为其它类的基类,则析构函数通常要设为virtual,然后在子类重写,以实现多态。因此析构函数不能设为private。还好C++提供了第三种访问控制,protected。将析构函数设为protected可以有效解决这个问题,类外无法访问protected成员,子类则可以访问。
二、类的使用很不方便,使用new建立对象,却使用destory函数释放对象,而不是使用delete。(使用delete会报错,因为delete对象的指针,会调用对象的析构函数,而析构函数类外不可访问)这种使用方式比较怪异。
为了统一,不能调用delete析构,也不让调用new来创建对象。可以将构造函数设为protected,然后提供一个public的static函数来完成构造,这样不使用new,而是使用一个函数来构造,使用一个函数来析构。代码如下,类似于单例模式:
class A
{
protected:
A(){}
~A(){}
public:
static A* create()
{
return new A();
}
void destory()
{
delete this;
}
};
这样,调用create()函数在堆上创建类A对象,调用destory()函数释放内存。
2、只能在栈上分配类对象
只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。虽然你不能影响new运算符的能力(因为那是C++语言内建的),但是你可以利用一个事实:new运算符总是先调用 operator new,而后者我们是可以自行声明重写的。因此,将operator new()设为私有即可禁止对象被new在堆上,还要注意一点,new运算符不能使用了,就意味着delete也不能使用了。代码如下:
class A
{
private:
void* operator new(size_t t){} // 注意函数的第一个参数和返回值都是固定的
void operator delete(void* ptr){} // 重载了new就需要重载delete
public:
A(){}
~A(){}
};
把operator delete私有化,这样可以防止一些人使用delete来释放静态建立的对象。如:
A a; //静态建立对象
A *ptr = &a;
delete ptr;
最后来看怎么禁止一个类被继承
在C++中子类的构造函数会调用父类的构造函数,子类的析构函数也会调用父类的析构函数,所以把一个类的构造函数和析构函数声明为private。当一个类试图从这个禁止被继承的类继承的时候,一定会由于构造函数和析构函数的访问权限而产生编译错误。
class SealedClass
{
private:
SealedClass(){}
~SealedClass(){}
public:
static SealedClass* getInstance()
{
return new SealedClass();
}
void Destroy()
{
delete this;
}
};
但是这种类跟我们其他的普通类很不一样,我不能用new去申请,也不能直接用delete去释放。同时还不能静态的在栈空间申请对象,只能在堆空间中。
下面看一种很舒服的设计技巧:
如何让对象只在堆或者栈中分配空间ANDC++禁止一个类被继承的更多相关文章
- 堆和栈 内存分配 heap stack
Java中的堆和栈 在[函数]中定义的一些[基本类型的变量]和[对象的引用变量]都是在函数的[栈内存]中分配的.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间, ...
- 【转载】c++中堆、栈内存分配
一.内存划分 1.栈区(stack)— 由编译器自动分配释放 ,存放函数参数值,局部变量值等.其操作方式类似于数据结构中栈.2.堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时 ...
- c++中堆、栈内存分配
转自:https://blog.csdn.net/qingtingchen1987/article/details/7698415 一个由C/C++编译程序占用内存分为以下几个部分1.栈区(stack ...
- java局部变量,成员变量在堆和栈中的存储
对于局部变量,如果是基本类型,会把值直接存储在栈:如果是引用类型,比如String s = new String("william");会把其对象存储在堆,而把这个对象的引用(指针 ...
- 缺少新的栈标识:报出异常FLAG_ACTIVITY_NEW_TASK flag-是由于activity关闭之后开启一个新的acitivity时没有标识在栈中,所以需要给一个栈标识
异常(栈里必须有activity的flag标识): 05-02 08:43:36.173: E/AndroidRuntime(3328): android.util.AndroidRuntimeExc ...
- JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 堆栈是栈 JVM栈和本地方法栈划分 Java中的堆,栈和c/c++中的堆,栈 数据结构层面的堆,栈 os层面 ...
- Java中堆与栈
简单的说:Java把内存划分成两种:一种是栈内存,一种是堆内存. 1:什么是堆内存: 堆内存是是Java内存中的一种,它的作用是用于存储Java中的对象和数组,当我们new一个对象或者创建一个数组的时 ...
- [转]Java中堆和栈创建对象的区别
转载自http://blog.csdn.net/hbhhww/article/details/8152838 栈与堆都是Java用来在Ram中存放数据的地方.与C++不同,Java自动管理栈和堆,程序 ...
- “吃人”的那些Java名词:对象、引用、堆、栈
记得中学的课本上,有一篇名为<狂人日记>课文:那时候根本理解不了鲁迅写这篇文章要表达的中心思想,只觉得满篇的“吃人”令人心情压抑:老师在讲台上慷慨激昂的讲,大多数的同学同我一样,在课本面前 ...
随机推荐
- 9款极具创意的HTML5/CSS3进度条动画(免积分下载)
尊重原创,原文地址:http://www.cnblogs.com/html5tricks/p/3622918.html 免积分打包下载地址:http://download.csdn.net/detai ...
- java中表示二进制、八进制、十进制、十六进制,double、float、整型
java里不能这样表示二进制,只能是 8,10,16进制 8: 前置 0 10: 不需前置 16: 前置 0x 或者 0X double:2 ...
- [android开发IDE]adt-bundle-windows-x86的一个bug:无法解析.rs文件--------rs_core.rsh file not found
google的android自带的apps写的是相当牛逼的,将其导入到eclipse中方便我们学习扩展.可惜关于导入的资料太少了,尤其是4.1之后的gallery和camera合二为一了.之前导4.0 ...
- O2O领域添新军,正品网加快布局的战略考量
前不久.正品网採购虚拟运营商30万170号码的招标公告引发了业界的广泛关注.一方面,虚拟运营商正处于"徘徊不定"的十字路口.据业内知情人士透露,眼下12家虚拟运营商企业共放号在20 ...
- POJ2528线段树基础
開始就直接用延迟标记搞了下.最后发现内存肯定会爆了.数据太大了. 问了瓜神,原来应该用离散化来做这题,详细见凝视 #include <cstdio> #include <cstrin ...
- DevExpress中ChartControl柱状图(Bar)用法
我的数据 代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 chartControl1.Series.Clear(); ...
- oracle实现今年数据 去年同期和增长百分比
select c.*,round((datanow-databefore)/databefore,2)*100||'%' datapercent from (select a.auth_tztype ...
- JavaScript之JSON
一.简介:Json是JavaScript中读取结构化数据更好的方式.因为Json数据可以直接传给eval(),而且不必创建DOM对象.Json是一种数据格式,不是一种编程语言,虽然具有相同的语法形式, ...
- iOS - SQLite Database 操作数据库
iOS - SQLite Database 操作数据库 Sqlite 能被用在ios上做数据处理用,只要你懂得一点sql 就很容易使用sqlite 1:创建一个简单的View based appl ...
- [Script]EBS里查看模块的版本、文件的版本信息【Z】
系统版本信息 装了哪些模块,以及版本信息 select 'Current Application Release: '||ver||' ('||bug||')' "Description&q ...