只在堆上或只在栈上定义对象
- class OnlyHeapClass
- {
- public:
- OnlyHeapClass()
- {
- }
- void Destroy()
- {
- delete this; // 等效于"OnlyHeapClass::~OnlyHeapClass();", 写
- // 成"OnlyHeapClass::~OnlyHeapClass();"更容易理
- // 解public成员函数调用private析构函数.
- }
- private:
- ~OnlyHeapClass()
- {
- }
- };
- int main()
- {
- OnlyHeapClass *pInst = new OnlyHeapClass;
- pInst ->Destroy(); // 如果类中没有定义Destroy()函数, 而在这里用"delete pInst;"代
- // 替"pInst->Destroy();", 则会报错. 因为"delete pInst;"会去调
- // 用类的析构函数, 而在类域外调用类的private成员函数必然会报错.
- return 0;
- }
原因是C++是一个静态绑定的语言. 在编译过程中, 所有的非虚函数调用都必须分析完成. 即使是虚函数, 也需检查可访问性. 因些, 当在栈(stack)上生成对象时, 对象会自动析构, 也就说析构函数必须可以访问. 而堆上生成对象, 由于析构时机由程序员控制, 所以不一定需要析构函数. 保证了不能在栈上生成对象后, 需要证明能在堆上生成它. 这里OnlyHeapClass与一般对象唯一的区别在于它的析构函数为私有, delete操作会调用析构函数, 所以不能编译.
那么如何释放它呢? 答案也很简单, 提供一个成员函数, 完成delete操作. 在成员函数中, 析构函数是可以访问的, 当然detele操作也是可以编译通过.
- void OnlyHeapClass::Destroy()
- {
- delete this;
- }
另外重载delete, new为私有可以达到要求对象创建于栈上的目的, 用placement new也可以创建在栈上.
- / 下面灰色字体系转载帮助理解之用 /
上面已经提到, 你决定禁止产生某种类型的堆对象, 这时你可以自己创建一个资源封装类, 该类对象只能在栈中产生, 这样就能在异常的情况下自动释放封装的资源.
class Resource ; // 代表需要被封装的资源类
class NoHashObject
{
private:
Resource *ptr ; // 指向被封装的资源
// ... //其它数据成员
void* operator new(size_t size) //非严格实现, 仅作示意之用
{
return malloc(size);
}
void operator delete(void* pp) //非严格实现, 仅作示意之用
{
free(pp);
}
public:
NoHashObject()
{
// 此处可以获得需要封装的资源, 并让ptr指针指向该资源
ptr = new Resource();
}
~NoHashObject()
{
delete ptr; // 释放封装的资源
}
};
NoHashObject现在就是一个禁止堆对象的类了, 如果你写下如下代码:
NoHashObject* fp = new NoHashObject(); // 编译期错误!
delete fp;
上面代码会产生编译期错误. 好了, 现在你已经知道了如何设计一个禁止堆对象的类了, 你也许和我一样有这样的疑问, 难道在类NoHashObject的定义不能改变的情况下, 就一定不能产生该类型的堆对象了吗? 不, 还是有办法的, 我称之为“暴力破解法”. C++是如此地强大, 强大到你可以用它做你想做的任何事情. 这里主要用到的是技巧是指针类型的强制转换.
int main()
{
char* temp = new char[sizeof(NoHashObject)] ;
//强制类型转换, 现在ptr是一个指向NoHashObject对象的指针
NoHashObject* obj_ptr = (NoHashObject*)temp ;
temp = NULL ; //防止通过temp指针修改NoHashObject对象
//再一次强制类型转换, 让rp指针指向堆中NoHashObject对象的ptr成员
Resource* rp = (Resource*)obj_ptr ;
//初始化obj_ptr指向的NoHashObject对象的ptr成员
rp = new Resource() ;
//现在可以通过使用obj_ptr指针使用堆中的NoHashObject对象成员了
... ...
delete rp ;//释放资源
temp = (char*)obj_ptr ;
obj_ptr = NULL ;//防止悬挂指针产生
delete [] temp ;//释放NoHashObject对象所占的堆空间.
return 0;
}
某块内存中的数据是不变的, 而类型就是我们戴上的眼镜, 当我们戴上一种眼镜后, 我们就会用对应的类型来解释内存中的数据, 这样不同的解释就得到了不同的信息.
所谓强制类型转换实际上就是换上另一副眼镜后再来看同样的那块内存数据.
另外要提醒的是, 不同的编译器对对象的成员数据的布局安排可能是不一样的, 比如, 大多数编译器将NoHashObject的ptr指针成员安排在对象空间的头4个字节, 这样才会保证下面这条语句的转换动作像我们预期的那样执行:
Resource* rp = (Resource*)obj_ptr ;
但是, 并不一定所有的编译器都是如此.
既然我们可以禁止产生某种类型的堆对象, 那么可以设计一个类, 使之不能产生栈对象吗? 当然可以.
五.禁止产生栈对象
前面已经提到了, 创建栈对象时会移动栈顶指针以“挪出”适当大小的空间, 然后在这个空间上直接调用对应的构造函数以形成一个栈对象, 而当函数返回时, 会调用其析构函数释放这个对象, 然后再调整栈顶指针收回那块栈内存. 在这个过程中是不需要operator new/delete操作的, 所以将operator new/delete设置为private不能达到目的. 当然从上面的叙述中, 你也许已经想到了: 将构造函数或析构函数设为私有的, 这样系统就不能调用构造/析构函数了, 当然就不能在栈中生成对象了.
这样的确可以, 而且我也打算采用这种方案. 但是在此之前, 有一点需要考虑清楚,那就是, 如果我们将构造函数设置为私有, 那么我们也就不能用new来直接产生堆对象了, 因为new在为对象分配空间后也会调用它的构造函数啊. 所以, 我打算只将析构函数设置为private. 再进一步, 将析构函数设为private除了会限制栈对象生成外, 还有其它影响吗? 是的, 这还会限制继承.
如果一个类不打算作为基类, 通常采用的方案就是将其析构函数声明为private.
为了限制栈对象, 却不限制继承, 我们可以将析构函数声明为protected, 这样就两全其美了. 如下代码所示:
class NoStackObject
{
protected:
~NoStackObject() { }
public:
void destroy()
{
delete this ;//调用保护析构函数
}
};
接着, 可以像这样使用NoStackObject类:
NoStackObject* hash_ptr = new NoStackObject() ;
... ... //对hash_ptr指向的对象进行操作
hash_ptr->destroy() ;
呵呵, 是不是觉得有点怪怪的, 我们用new创建一个对象, 却不是用delete去删除它, 而是要用destroy方法. 很显然, 用户是不习惯这种怪异的使用方式的. 所以, 我决定将构造函数也设为private或protected. 这又回到了上面曾试图避免的问题, 即不用new, 那么该用什么方式来生成一个对象了? 我们可以用间接的办法完成, 即让这个类提供一个static成员函数专门用于产生该类型的堆对象. (设计模式中的singleton模式就可以用这种方式实现. )让我们来看看:
class NoStackObject
{
protected:
NoStackObject() { }
~NoStackObject() { }
public:
static NoStackObject* creatInstance()
{
return new NoStackObject() ;//调用保护的构造函数
}
void destroy()
{
delete this ;//调用保护的析构函数
}
};
现在可以这样使用NoStackObject类了:
NoStackObject* hash_ptr = NoStackObject::creatInstance() ;
... ... //对hash_ptr指向的对象进行操作
hash_ptr->destroy() ;
hash_ptr = NULL ; //防止使用悬挂指针
现在感觉是不是好多了, 生成对象和释放对象的操作一致了.
只在堆上或只在栈上定义对象相关推荐
- 堆和栈的区别,有一个64k的字符串,是放到堆上,还是放到栈上,为什么?
堆和栈的区别,有一个64k的字符串,是放到堆上,还是放到栈上,为什么? 答:只有引用及基本数据类型是直接存在栈上.对象类型可能是在堆.方法区.常量池中. 如二楼所说,放到堆中还是放到栈中,jvm会根据 ...
- C++ 如何一次在堆上申请4G的内存?如何设计一个类只能在堆或者栈上创建对象?
1.如何一次在堆上申请4G的内存? 因为32位的环境下虚拟地址空间的大小只有4g,而光内核空间就需要1g,所以不可能申请得到,只有在64位的环境下才可以实现,只需要把执行环境改为64x即可 #incl ...
- C++:在堆上创建对象,还是在栈上?
这篇文章来自于一次讨论:http://www.devbean.net/2013/01/qt-study-road-2-model-view/#comment-17532.关于究竟是在堆上还是在栈上创建 ...
- JVM 的栈上分配、TLAB、PLAB 有啥区别?
我们在学习 G1 回收器的时候,一般我们都会接触到 TLAB 和 PLAB 这两个术语.它们都是为了提高内存分配效率而存在的,但它们和栈上分配有什么区别呢?今天,就让树哥带着大家盘一盘. 栈上分配 稍 ...
- 栈上分配和TLAB的区别
栈上分配 JVM中,栈上空间为线程私有,堆上空间为全局共享.所以大部分对象存在于堆上,线程通过栈上的引用指向堆上对象的内存地址.堆上没有任何引用关系的对象会被JVM标记后GC掉. 很多对象不存在逃逸现 ...
- 关于栈上分配和TLAB的理解
引言 我们知道,一般在java程序中,new的对象是分配在堆空间中的,但是实际的情况是,大部分的new对象会进入堆空间中,而并非是全部的对象,还有另外两个地方可以存储new的对象,我们称之为栈上分配以 ...
- C/Cpp / 如何定义一个只能在堆上(栈上)生成对象的类?
在 C++ 中,类的对象建立分为两种,一种是静态建立,如 A a:另一种是动态建立,如 A* ptr = new A:这两种方式是有区别的. 静态建立一个类对象,是由编译器为对象在栈空间中分配内存,是 ...
- 只能在栈上或者堆上创建对象
只允许对象生成于堆内?怎么理解?肿么办?假如,手头上有一个类Person,当你在程序中写下Person rn时,编译器悄悄地做了两件事:调用constructor构造对象rn,而在弹栈时,调用dest ...
- C++ 特殊类设计:只能在堆、栈上创建的类、无法继承的类、无法拷贝的类、只能创建一个对象的类
文章目录 请设计一个类,不能被拷贝 请设计一个类,不能被继承 请设计一个类,只能在堆上创建对象 请设计一个类,只能在栈上创建对象 请设计一个类,只能创建一个对象 饿汉模式 懒汉模式 请设计一个类,不能 ...
最新文章
- java 异步返回_在Java中使用异步后,主方法如何返回异步中得到的值?
- PMBOK项目管理PMI主义\IPMA概述
- Delphi控件之---UpDown以及其与TEdit的配合使用(比如限制TEdit只能输入数字,还有Object Inspector之组件属性的介绍)...
- NYOJ 595 乱七八糟
- 阮一峰react demo代码研究的学习笔记 - demo7 debug - how ref node is attached
- python图像边缘检测_使用python获取图像中形状的轮廓(x,y)坐标
- SparkSql学习笔记(包含IDEA编写的本地代码)
- 【阅读笔记】频率视角下的机器学习
- 安卓post 提交图片流和字符数据
- iOS之HealthKit使用
- linux嵌入式工控机编程,Linux嵌入式工控机的特点
- ReactNative实现仿微信或者通讯录快速索引功能
- 企业如何制作自己的公司网站?
- RPGMAKER游戏引擎基于JavaScript的插件制作(六)——重写方法(三):在场景(scenes)中创建精灵(Sprite)——复制式重写的实例教学
- Android使用addr2line定位native崩溃堆栈
- Unity Addressable学习笔记一(整体介绍)
- C++无符号数比较大小
- android手机如何获取手机号
- python创建虚拟环境慢_小灶时间-如果你还不会用Python虚拟环境
- Studio One6中文语言版DAW数字音频音乐创作软件