控制对象的创建方式(禁止创建栈对象or堆对象)和创建的数量
我们知道,C++将内存划分为三个逻辑区域:堆、栈和静态存储区。既然如此,我称位于它们之中的对象分别为堆对象,栈对象以及静态对象。通常情况下,对象创建在堆上还是在栈上,创建多少个,这都是没有限制的。但是有时会遇到一些特殊需求。
1.禁止创建栈对象
禁止创建栈对象,意味着只能在堆上创建对象。创建栈对象时会移动栈顶指针以“挪出”适当大小的空间,然后在这个空间上直接调用类的构造函数以形成一个栈对象。而当栈对象生命周期结束,如栈对象所在函数返回时,会调用其析构函数释放这个对象,然后再调整栈顶指针收回那块栈内存。在这个过程中是不需要operator new/delete操作的,所以将operator new/delete设置为private不能达到目的。
可以将构造函数或析构函数设为私有的,这样系统就不能调用构造/析构函数了,当然就不能在栈中生成对象了。这样的确可以,但有一点需要注意,那就是如果我们将构造函数设置为私有,那么我们也就不能用new来直接产生堆对象了,因为new在为对象分配空间后也会调用它的构造函数。所以,如果将构造函数和析构函数都声明为private会带来较大的副作用,最好的方法是将析构函数声明为private,而构造函数保持为public。
再进一步,将析构函数设为private除了会限制栈对象生成外,还有其它影响吗?是的,这还会限制继承。如果一个类不打算作为基类,通常采用的方案就是将其析构函数声明为private。为了限制栈对象,却不限制继承,我们可以将析构函数声明为protected,这样就两全其美了。如下代码所示:
class NoStackObject{
protected:
~NoStackObject(){}
public:
void destroy(){
delete this ;//调用保护析构函数
}
};
上面的类在创建栈对象时,如NoStackObject obj;
时编译将会报错,而采用new的方式,编译就会通过。需要注意一点的是,通过new创建堆对象时,在手动释放对象内存时,我们需要调用其析构函数,这时就需要一点技巧来辅助——引入伪析构函数destory,如上面的代码所示。
方法拓展。
仔细一看,我们会发现上面的方法让人别扭。我们用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 ; //防止使用悬挂指针
现在感觉是不是好多了,生成对象和释放对象的操作一致了。
2.禁止创建堆对象
我们已经知道,产生堆对象的唯一方法是使用new操作,如果我们禁止使用new不就行了么。再进一步,new操作执行时会调用operator new,而operator new是可以重载的。方法有了,就是使new operator 为private,为了对称,最好将operator delete也重载为private。
class NoStackObject{
private:
static void* operator new(size_t size);
static void operator delete(void* ptr);
};
//用户代码
NoStackObject obj0; //OK
static NoStackObject obj1; //OK
NoStackObject * pObj2 = new NoStackObject; //ERROR
如果也想禁止堆对象数组,可以把operator new[]和operator delete[]也声明为private。
这里同样在继承时存在问题,如果派生类改写了operator new和operator delete并声明为public,则基类中原有的private版本将失效,参考如下代码:
class NoStackObject{
protected:
static void* operator new(size_t size);
static void operator delete(void* ptr);
};
class NoStackObjectSon:public NoStackObject{
public:
static void* operator new(size_t size){ //非严格实现,仅作示意之用
return malloc(size);
};
static void operator delete(void* ptr){ //非严格实现,仅作示意之用
free(ptr);
};
};
//用户代码
NoStackObjectSon* pObj2 = new NoStackObjectSon; //OK
3.控制实例化对象的个数
在游戏设计中,我们采用类CGameWorld作为游戏场景的抽象描述。然而在游戏运行过程中,游戏场景只有一个,也就是对CGameWorld对象的只有一个。对于对象的实例化,有一点是十分确定的:要调用构造函数。所以,如果想控制CGameWorld的实例化对象只有一个,最简单的方法就是将构造函数声明为private,同时提供一个static对象。如下:
class CGameWorld
{
public:
bool Init();
void Run();
private:
CGameWorld();
CGameWorld(const CGameWorld& rhs);
friend CGameWorld& GetSingleGameWorld();
};
CGameWorld& GetSingleGameWorld()
{
static CGameWorld s_game_world;
return s_game_world;
}
这个设计有三个要点:
(1)类的构造函数是private,阻止对象的建立;
(2)GetSingleGameWorld函数被声明为友元,避免了私有构造函数引起的限制;
(3)s_game_world为一个静态对象,对象唯一。
当用到CGameWorld的唯一实例化对象时,可以如下:
GetSingleGameWorld().Init();
GetSingleGameWorld().Run();
如果有人对GetSingleGameWorld是一个全局函数有些不爽,或者不想使用友元,将其声明为类CGameWorld的静态函数也可以达到目的,如下:
class CGameWorld
{
public:
bool Init();
void Run();
static CGameWorld& GetSingleGameWorld();
private:
CGameWorld();
CGameWorld(const CGameWorld& rhs);
};
这就是设计模式中著名的单件模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
如果我们想让对象产生的个数不是一个,而是最大为N(N>0)个。可以在类内部设置一个静态计数变量,在调用构造函数时,该变量加1,当调用析构函数时,该变量减1。如下:
class CObject
{
public:
CObject();
~CObject();
private:
static size_t m_nObjCount;
...
};
CObject::CObject()
{
if (m_nObjCount > N)
throw;
m_nObjCount++;
}
CObject::~CObject()
{
m_nObjCount--;
}
size_t CObject::m_nObjCount;
掌握控制类的实例化对象个数的方法。当实例化对象唯一时,采用设计模式中的单件模式;当实例化对象为N(N>0)个时,设置计数变量是一个思路。
阅读上面的示例代码还需要注意抛出异常时没有对象,即throw后没有对象,如果在catch块中或者被catch块调用的函数中出现,表示重新抛出异常。没有操作数的 throw 表达式将重新引发当前正在处理的异常。 我们建议在重新引发异常时采用该形式,是因为这将保留原始异常的多态类型信息。 此类表达式只应在 catch 处理程序中或从 catch 处理程序调用的函数中使用。
Such an expression should only be used in a catch handler or in a function that’s called from a catch handler.重新引发的异常对象是原始异常对象,而不是副本。如果“throw后没有对象”出现在非catch块中,则表示抛出不能被捕获的异常,即使catch(…)也不能将其补捕获。
4.小结
堆对象,栈对象以及静态对象统称为内存对象,如果要把内存对象理解的更为深入,推荐看看《深入探索C++对象模型》这本书。
参考文献
[1]C++——内存对象 禁止产生堆对象 禁止产生栈对象
[2]李健.编写高质量代码:改善C++程序的150个建议.第一版.北京:机械工业出版社,2012.1:299-301
控制对象的创建方式(禁止创建栈对象or堆对象)和创建的数量的更多相关文章
- C++——内存对象 禁止产生堆对象 禁止产生栈对象
用C或C++写程序,需要更多地关注内存,这不仅仅是因为内存的分配是否合理直接影响着程序的效率和性能,更为主要的是,当我们操作内存的时候一不小心就会出现问题,而且很多时候,这些问题都是不易发觉的,比如内 ...
- Django-多对多关系的三种创建方式-forms组件使用-cookie与session-08
目录 表模型类多对多关系的三种创建方式 django forms 组件 登录功能手写推理过程 整段代码可以放过来 forms 组件使用 forms 后端定义规则并校验结果 forms 前端渲染标签组件 ...
- 多对多三种创建方式、forms组件、cookies与session
多对多三种创建方式.forms组件.cookies与session 一.多对多三种创建方式 1.全自动 # 优势:不需要你手动创建第三张表 # 不足:由于第三张表不是你手动创建的,也就意味着第三张表字 ...
- Java多线程学习(二)---线程创建方式
线程创建方式 摘要: 1. 通过继承Thread类来创建并启动多线程的方式 2. 通过实现Runnable接口来创建并启动线程的方式 3. 通过实现Callable接口来创建并启动线程的方式 4. 总 ...
- Django之ORM多对多表创建方式,AJAX异步提交,分页器组件等
MTV与MVC MTV模型: M:模型层(models.py),负责业务对象和数据库关系的映射(ORM) T:模板层(Template),负责如何把页面展示给用户(HTML) V:视图层( ...
- Java多线程——线程的创建方式
Java多线程——线程的创建方式 摘要:本文主要学习了线程的创建方式,线程的常用属性和方法,以及线程的几个基本状态. 部分内容来自以下博客: https://www.cnblogs.com/dolph ...
- Django多对多表的三种创建方式,MTV与MVC概念
MTV与MVC MTV模型(django): M:模型层(models.py) T:templates V:views MVC模型: M:模型层(models.py) V:视图层(views.py) ...
- 【20190220】JavaScript-知识点整理:对象创建方式、原型、闭包
一.对象创建方式 1. 工厂模式 这种模式抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节.存在的问题是无法通过 instanceof 识别一个对象的类型. function creat ...
- javascript一种新的对象创建方式-Object.create()
1.Object.create() 是什么? Object.create(proto [, propertiesObject ]) 是E5中提出的一种新的对象创建方式,第一个参数是要继承的原型,如果不 ...
随机推荐
- JavaScript学习要点
Javascript相关内容 1.序列化--json - stringify() 将对象转换为字符串 - parse() 将字符串转换为对象 list=[11,22,33,44,55]; 结果:(5) ...
- DNS分离解析IPV6与IPV4用户
IPV6改造中经常会遇到,网站使用了CDN,但是CDN厂商还不支持IPV6的情况,而AAAA.A.CNAME记录互相冲突,想实现IPV6用户得到AAAA记录,IPV4用户得到CNAME记录的需求. 解 ...
- 深度学习-tensorflow学习笔记(1)-MNIST手写字体识别预备知识
深度学习-tensorflow学习笔记(1)-MNIST手写字体识别预备知识 在tf第一个例子的时候需要很多预备知识. tf基本知识 香农熵 交叉熵代价函数cross-entropy 卷积神经网络 s ...
- Webstorm使用时发生Page 'http://localhost:63340/n…tok/css/bootstrap.css.map' requested without authorization, you can copy URL and open it in browser to trust it.
在使用webstorm编辑器开发时候,点击4处发生以下错误: Page 'http://localhost:63340/n…tok/css/bootstrap.css.map' requested w ...
- Beta版本互评
基于NABCD评论作品,及改进建议 经过alpha发布之后,迫不及待的使用了psp daily这款软件,使用非常方便,基本的功能都可以实现,经过beta周之后,我对这款产品非常期待,希望能给我更友好的 ...
- No.100_第一次团队会议
任务的确立 这次会议,我们的主要目标是确定任务: 我们的任务有以下几个选择: 学霸网站,这个项目拥有以前的前端代码,我们再使用Django后端服务.上手难度较低,环境较好. 多平台时间管理软件. 安卓 ...
- 使用sqlyog创建数据库的错误
1.错误代码: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL s ...
- 冲刺One之站立会议4 /2015-5-17
今天我们继续了昨天未完成的部分,把服务器端的在线人数显示做了出来,但是在调试的时候还有一些不可预知的自己也不会改的bug,让我们有点不知所措,启动时间的显示相对来说比较容易实现. 燃尽图4
- WebGL七点二
与上一节相比这一节相当做了些整合和整理,目的是提高了读写效率和减少代码量,就是做了优化.这里我们只需要在顶点着色器中定义一个用于从js传递参数的变量u_MvcMatrix一看就知道是uniform变量 ...
- SGU 223 little kings BSOJ2772 状压DP
1896 [SCOI2005]互不侵犯King [问题描述]在n*n(1<=n<=10)的棋盘上放k(0<=k<=n*n)个国王(可攻击相邻的8 个格子),求使它们无法互相攻击 ...