参考:C++ 内存分配(new,operator new)详解

如何限制对象只能建立在堆上或者栈上

new运算符和operator new()

new:指我们在C++里通常用到的运算符,比如A* a = new A;  对于new来说,有new和::new之分,前者位于std

operator new():指对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运算符中完成的

new和operator new之间的关系

A* a = new A;我们知道这里分为两步:1.分配内存,2.调用A()构造对象。事实上,分配内存这一操作就是由operator new(size_t)来完成的,如果类A重载了operator new,那么将调用A::operator new(size_t ),如果没有重载,就调用::operator new(size_t ),全局new操作符由C++默认提供。因此前面的两步也就是:1.调用operator new 2.调用构造函数。

(1)new :不能被重载,其行为总是一致的。它先调用operator new分配内存,然后调用构造函数初始化那段内存。

new 操作符的执行过程:
1. 调用operator new分配内存 ;
2. 调用构造函数生成类对象;
3. 返回相应指针。

(2)operator new:要实现不同的内存分配行为,应该重载operator new,而不是new。

operator new就像operator + 一样,是可以重载的。如果类中没有重载operator new,那么调用的就是全局的::operator new来完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重载的。

如何限制对象只能建立在堆上或者栈上

在C++中,类的对象建立分为两种,一种是静态建立,如A a;另一种是动态建立,如A* ptr=new A;这两种方式是有区别的。

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

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

那么如何限制类对象只能在堆或者栈上建立呢?下面分别进行讨论。

只能建立在堆上(设置析构函数为Protected)

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

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

当对象建立在栈上面时,是由编译器分配内存空间的,调用构造函数来构造栈对象。当对象使用完后,编译器会调用析构函数来释放栈对象所占的空间。编译器管理了对象的整个生命周期。如果编译器无法调用类的析构函数,情况会是怎样的呢?比如,类的析构函数是私有的,编译器无法调用析构函数来释放内存。所以,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。

因此,将析构函数设为私有,类对象就无法建立在栈上了。代码如下:

class A
{
public:
A(){}
void destory(){delete this;}
private:
~A(){}
};

试着使用A a;来建立对象,编译报错,提示析构函数无法访问。这样就只能使用new操作符来建立对象,构造函数是公有的,可以直接调用。类中必须提供一个destory函数,来进行内存空间的释放。类对象使用完成后,必须调用destory函数。

上述方法的一个缺点就是,无法解决继承问题。如果A作为其它类的基类,则析构函数通常要设为virtual,然后在子类重写,以实现多态。因此析构函数不能设为private。还好C++提供了第三种访问控制,protected。将析构函数设为protected可以有效解决这个问题,类外无法访问protected成员,子类则可以访问。

另一个问题是,类的使用很不方便,使用new建立对象,却使用destory函数释放对象,而不是使用delete。(使用delete会报错,因为delete对象的指针,会调用对象的析构函数,而析构函数类外不可访问)这种使用方式比较怪异。为了统一,可以将构造函数设为protected,然后提供一个public的static函数来完成构造,这样不使用new,而是使用一个函数来构造,使用一个函数来析构。代码如下,类似于单例模式:

class A
{
protected:
A(){}
~A(){}
public:
static A* create()
{
return new A();
}
void destory()
{
delete this;
}
};

这样,调用create()函数在堆上创建类A对象,调用destory()函数释放内存。

只能建立在栈上(重载new函数设为私有)

只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。将operator new()设为私有即可。代码如下:

class A
{
private:
void* operator new(size_t t){} // 注意函数的第一个参数和返回值都是固定的
void operator delete(void* ptr){} // 重载了new就需要重载delete
public:
A(){}
~A(){}
};

C++ 内存分配(new,operator new)详解的更多相关文章

  1. [转载]windows任务管理器中的工作设置内存,内存专用工作集,提交大小详解

    windows任务管理器中的工作设置内存,内存专用工作集,提交大小详解 http://shashanzhao.com/archives/832.html 虽然是中文字,但是理解起来还是很困难,什么叫工 ...

  2. c++中内存拷贝函数(C++ memcpy)详解

    原型:void*memcpy(void*dest, const void*src,unsigned int count); 功能:由src所指内存区域复制count个字节到dest所指内存区域. 说明 ...

  3. 【转】C++ 内存分配(new,operator new)详解

    本文主要讲述C++ new运算符和operator new, placement new之间的种种关联,new的底层实现,以及operator new的重载和一些在内存池,STL中的应用. 一 new ...

  4. windows任务管理器中的工作设置内存,内存专用工作集,提交大小详解

    虽然是中文字,但是理解起来还是很困难,什么叫工作设置内存,什么叫内存专用工作集,什么叫提交大小,区别是什么,让人看了一头雾水. 通俗的讲工作设置内存是程序占用的物理内存(包含与其他程序共享的一部分), ...

  5. Bitmap之内存缓存和磁盘缓存详解

    原文首发于微信公众号:躬行之(jzman-blog) Android 中缓存的使用比较普遍,使用相应的缓存策略可以减少流量的消耗,也可以在一定程度上提高应用的性能,如加载网络图片的情况,不应该每次都从 ...

  6. Android内存优化(三)详解内存分析工具MAT

    前言 在这个系列的前四篇文章中,我分别介绍了DVM.ART.内存泄漏和内存检测工具的相关知识点,这一篇我们通过一个小例子,来学习如何使用内存分析工具MAT. 1.概述 在进行内存分析时,我们可以使用M ...

  7. tomcat内存配置及配置参数详解

    1.jvm内存管理机制: 1)堆(Heap)和非堆(Non-heap)内存 按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配.堆是在 Java 虚拟 ...

  8. c++内存中字节对齐问题详解

    一.什么是字节对齐,为什么要对齐?    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址 ...

  9. 内存管理-MRC与ARC详解

    Objective-C提供了两种内存管理机制MRC(Mannul Reference Counting)和ARC(Automatic Reference Counting),为Objective-C提 ...

随机推荐

  1. 内存操作函数memmove,memcpy,memset

    通过字符串的学习,我们知道字符串操作函数的操作对象是字符串,并且它的结束标志是结束符\0,当然这个说的是不 受限制的字符串函数.然而当我们想要将一段内存的数据复制到另一块内存时,我们不能使用字符串操作 ...

  2. bzoj千题计划125:bzoj1037: [ZJOI2008]生日聚会Party

    http://www.lydsy.com/JudgeOnline/problem.php?id=1037 一个区间是否满足 任意连续的一段,男孩与女孩的数目之差不超过k, 取决于男孩与女孩数目之差的最 ...

  3. memcmp 和 memcpy使用

    #include <iostream> #include <cstring> #include <cstdlib> #include <cstdio> ...

  4. Writing your first academic paper

    Writing your first academic paper If you are working in academics (and you are if you are working wi ...

  5. java8 write file 写文件

    1.用BufferedWriter写入文件 //Get the file reference Path path = Paths.get("c:/output.txt"); //U ...

  6. 漂亮!Javascript代码模仿淘宝宝贝搜索结果的分页显示效果

    分页按钮思想: 1.少于9页,全部显示 2.大于9页,1.2页显示,中间页码当前页为中心,前后各留两个页码 先看效果图: 01输入框焦点效果 02效果 模仿淘宝的分页按钮效果控件kkpager  JS ...

  7. 当今最流行的Web项目管理工具精选

    代码管理 以前各种开源项目的代码都是通过博客和个人网页来发布的.这种分享方式并不是最容易的一种,也不便于他人对代码做出贡献.下面是几个管理项目代码的工具,不管对于个人开发者还是团队开发者来说,它们都是 ...

  8. linux学习记录.3.virtualbox 共享文件夹

    需要先安装增强功能. 设置目录后, mkdir /mnt/WinDownload      //建立映射目录 sudo mount -t vboxsf Download /mnt/Windwnload ...

  9. SQL Server 问题之 排序规则(collation)冲突

    一.写在前面 最近公司进行开发环境升级,数据库也准备了一个新的服务器,一切准备好后开始数据迁移,采取的方式为对现有Database(现有服务器Windows Server 2003 + SQL Ser ...

  10. 关于注入抽象类报could not autowire field的问题

    昨天工作中遇到了一个很奇葩的问题,之前一直都没考虑过抽象类这块,一直用的注入接口实现类: 先看下错误: 因为在类中注入了一个抽象类,之前只有一个继承子类,所以没问题,这里要说一下抽象类的实例化: 抽象 ...