C++中new有三种形式:new operator、operator new和placement new。

1. new operator

new operator就是我们平时使用的new表达式,来为特定类型分配内存,并在新分配的内存中构造该类型的一个对象。e.g.

Employee* sp = new Employee("001");

它实际上发生了三个步骤:

  1. 该表达式调用名为operator new的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;
  2. 运行该类型的一个构造函数,用指定初始化式构造对象;
  3. 返回指向新分配并构造的对象的指针。

伪代码就是:

4. Employee* sp = (Employee*)malloc(sizeof(Employee));
5. sp->Employee::Employee("001");
6. return sp;

当使用delete表达式delete sp;删除动态分配对象的时候,发生两个步骤:

  1. 对指向对象运行适当的析构函数;
  2. 通过调用名为operator delete的标准库函数释放该对象所用内存。

调用operator delete函数不会运行析构函数,它只释放指定的内存。

new和delete表达式的行为是固定的,不能重定义的。

2. operator new

默认情况下,new表达式通过调用由标准库定义的operator new版本分配内存。我们也可以通过定义自定义类型的名为operator newoperator delete的成员函数,管理用于自身类型的内存。

编译器看到类类型的new或delete表达式的时候,它查看该类是否有operator newoperator delete成员,如果类定义(或继承)了自己的成员new和delete函数,则使用那些函数为对象分配和释放内存;否则,调用这些函数的标准库版本。

重载的operator newoperator delete成员函数必须是静态的,因为它们要么在构造对象之前使用,要么在撤销对象之后使用,因此,这些函数没有成员函数可操纵。像任意其他静态成员函数一样,new和delete只能直接访问所属类的静态成员。

operator new无法满足某一内存分配需求时,以前它会返回一个null指针,新的operator new会调用一个用户指定的错误处理函数new-handler,如果new-handler为null,就会抛出异常。

用户可以通过set_new_handler来指定自定义的new_handler

namespace std{typedef void (*new_handler)();new_handler set_new_handler(new_handler p) throw();
}

new_handler是个typedef,定义一个函数指针,该函数没有参数也不返回任何东西。set_new_handler参数是指定的new_handler函数,返回值是正在执行(但马上就要被替换)的那个new_handler函数。throw()表示该函数不抛出任何异常。

可以这样使用set_new_handler:

void outOfMem()
{std::cerr << "Unable to satisfy request for memeory\n";std::abort();
}int main()
{std::set_new_handler(outOfMem);int* pBigDataArray = new int[100000000L];...
}

如果一定要通过operator new的返回值判断是否成功分配内存,则可以使用operator newnothrow形式:

Employee* pe = new (std::nothrow) Employee;
if(pe == 0)
{...
}

看起来似乎可以工作,但是nothrow new对异常的强制保证性并不高。因为就算operator new正常分配内存,在Employee的构造函数里面也可能去调用new申请内存,那么这时抛出异常会一如往常地传播。所以,使用nothrow new只能保证operator new不抛出异常,不保证像“new (std::nothrow) Employee”这样的表达式绝不导致异常。

下面给出一个较为符合常规的operator new重载版本伪代码:

void* operator new(std::size_t size) throw(std::bad_alloc)
{using namespace std;if (size == 0){size = 1;   //处理0-byte申请。将它视为1-byte申请}while(true){尝试分配size bytes;if (分配成功)return (一个指针,指向分配得来的内存);//分配失败;找出目前的new-handling函数new_handler globalHandler = set_new_handler(0);set_new_handler(globalHandler);if (globalHandler) (*globalHandler)();else throw std::bad_alloc();}
}

将0-byte申请视为1-byte申请是因为,C++规定,即使用户要求0byte,operator new也得返回一个合法指针。

上述while循环退出的唯一办法是:内存被成功分配或new-handler函数让更对内存可用、安装另一个new-handler、卸除new-handler、抛出bad_alloc异常,或是承认失败而直接return。

针对class X设计的operator new,其往往只为大小为sizeof(X)的对象设计。如果被继承,则应改为采用标准operator new:

class Base
{public:static void* operator new(std::size_t size) throw(std::bad_alloc);...
};class Derived: public:Base //假设Derived未声明operator new
{ ... };Derived* p = new Derived; //这里调用的是Base::operator newvoid* Base::operator new(std::size_t size) throw(std::bad_alloc)
{if (size != sizeof(Base))return ::operator new(size);
}

对于operator delete,只需要记住:1.C++保证“删除null指针永远安全”;2.考虑被继承情况。

void Base::operator delete(void* rawMemory, std::size_t size) throw()
{if (rawMemory == 0) return;if (size != sizeof(Base)){::operator delete(rawMemory);return;}现在,归还rawMemory所指的内存;return;
}

3. placement new

placement new在已分配的原始内存中初始化一个对象,它与new的其他版本的不同之处在于,它不分配内存。相反,它接受指向已分配但未构造内存的指针,并在该内存中初始化一个对象。
e.g.

Employee* pe1 = new Employee;
Employee* pe2 = new (pe1) Employee;char buff[sizeof(Employee)];
Employee* pe3 = new (buff) Employee;
pe3->~Employee();

placement new构造对象的地址,可以是在栈上,也可以是在堆上。在栈上使用时,placement new需要手动调用析构函数。

STL中vector就是使用类似placement new的工作原理,先分配一大块内存,然后逐次在预分配内存中下一个可用位置初始化一个对象。

实际上,一般性术语“placement new”是指除了size_t,带任意额外参数的new。如:

class Employee
{public:static void* operator new(std::size_t, std::ostream& logStream) throw(std::bad_alloc);
}//调用operator new并传递cerr为其ostream实参;这个动作会在Employee构造函数抛出异常是泄露内存
Employee* pe = new (std::cerr) Employee;

如果内存分配成功,但是Employee构造函数抛出异常,运行期系统有责任取消operator new的分配并恢复旧观。然而运行期系统无法知道真正被调用的那个operator new如何运作,因此它无法取消分配并恢复旧观,所以上述做法行不通。取而代之的是,运行期系统寻找“参数个数和类型都与operator new相同”的某个operator delete。如果找到,那就是它的调用对象,如果找不到,那就作罢,这就造成内存泄露。

所以这里对应的operator delete应该是:

void operator delete(void*, std::ostream&) throw();

这种接受额外参数的operator delete就称为placement delete。

因此,如果定义了自定义的operator new/placement new成员函数,一定要对应的定义与之匹配的operator delete/placement delete。

由于成员函数的名称会掩盖其外围作用域的相同名称,所以如果自定义了operator/placement new,那么标准库版本的operator new就会被掩盖而无法调用。

class Employee
{public:static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc); //这个new会掩盖正常的global形式...
};Employee* pb = new Employee;  //Error! 正常形式的operator new被掩盖
Employee* pb = new (std::cerr) Employee; //Correct, 调用Employee的placement new

同样,derived class中的operator new也会掩盖global版本和基类的版本:

class Engineer: public Employee
{public:static void* operator new(std::size_t size) throw(std::bad_alloc); //重新声明正常形式的new...
};Engineer* pe = new (std::clog) Engineer; // Error! Employee的placement new被掩盖了
Engineer* pe = new Engineer; // Correct, 调用Engineer的operator new

缺省情况下,C++在global作用域内提供以下形式的operator new:

void* operator new(std::size_t) throw(std::bad_alloc); // normal new
void* operator new(std::size_t, void*) throw(); // placement new
void* operator new(std::size_t, const std::nothrow_t&) throw(); // nothrow new

如果你自定义了operator new,并且仍然想可以使用global的operator new,可以建立一个base class内含所有正常形式的new和delete,然后让你的类继承它:

class StandardNewDeleteForms
{public:// normal new/deletestatic void* operator new(std::size_t size) throw(std::bad_alloc){ return ::operator new(size); }static void operator delete(void* pMemory) throw(){ ::operator delete(pMemory); }// placement new/delete static void* operator new(std::size_t size, void* ptr) throw() { return ::operator new(size, ptr); } static void operator delete(void* pMemory, void* ptr) throw() { return ::operator delete(pMemory, ptr); } // nothrow new/delete static void* operator new(std::size_t size, const std::nothrow t& nt) throw() { return ::operator new(size, nt); } static void operator delete(void *pMemory, const std::nothrow_t&) throw() { ::operator delete(pMemory); }
};class Employee: public StandardNewDeleteForms
{public:using StandardNewDeleteForms::operator new;  //让这些形式可见using StandardNewDeleteForms::operator delete;static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc); // 自定义placement newstatic void operator delete(void* pMemory, std::ostream& logStream) throw(); //自定义placement delete...
};

参考资料:

  1. 《C++ Primer》
  2. 《Effective C++》

C++ - 深入理解new相关推荐

  1. 通用解题法——回溯算法(理解+练习)

    积累算法经验,积累解题方法--回溯算法,你必须要掌握的解题方法! 什么是回溯算法呢? 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就&quo ...

  2. stream流对象的理解及使用

    我的理解:用stream流式处理数据,将数据用一个一个方法去 . (点,即调用) 得到新的数据结果,可以一步达成. 有多种方式生成 Stream Source: 从 Collection 和数组 Co ...

  3. Linux shell 学习笔记(11)— 理解输入和输出(标准输入、输出、错误以及临时重定向和永久重定向)

    1. 理解输入和输出 1.1 标准文件描述符 Linux 系统将每个对象当作文件处理.这包括输入和输出进程.Linux 用文件描述符(file descriptor)来标识每个文件对象.文件描述符是一 ...

  4. java局部变量全局变量,实例变量的理解

    java局部变量全局变量,实例变量的理解 局部变量 可以理解为写在方法中的变量. public class Variable {//类变量static String name = "小明&q ...

  5. 智能文档理解:通用文档预训练模型

    预训练模型到底是什么,它是如何被应用在产品里,未来又有哪些机会和挑战? 预训练模型把迁移学习很好地用起来了,让我们感到眼前一亮.这和小孩子读书一样,一开始语文.数学.化学都学,读书.网上游戏等,在脑子 ...

  6. 熵,交叉熵,散度理解较为清晰

    20210511 https://blog.csdn.net/qq_35455503/article/details/105714287 交叉熵和散度 自己给自己编码肯定是最小的 其他的编码都会比这个 ...

  7. mapreduce理解_大数据

    map:对不同的数据进行同种操作 reduce:按keys 把数据规约到一起 看这篇文章请出去跑两圈,然后泡一壶茶,边喝茶,边看,看完你就对hadoop 与MapReduce的整体有所了解了. [前言 ...

  8. 文件句柄和文件描述符的区别和理解指针

    句柄是Windows用来标识被应用程序所建立或使用的对象的唯一整数,Windows使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等.Windows句柄有点象C语言中的文件句柄. ...

  9. 通俗理解条件熵-数学

    就是决策树里面选划分属性用到的计算 条件熵越小表示划分之后各个集合越纯净 前面我们总结了信息熵的概念通俗理解信息熵 - 知乎专栏,这次我们来理解一下条件熵. 我们首先知道信息熵是考虑该随机变量的所有可 ...

  10. 通俗理解tf.nn.conv2d() tf.nn.conv3d( )参数的含义 pytorhc 卷积

    20210609 例如(3,3,(3,7,7))表示的是输入图像的通道数是3,输出图像的通道数是3,(3,7,7)表示过滤器每次处理3帧图像,卷积核的大小是3 x 7 x 7. https://blo ...

最新文章

  1. 利用计算思维解决问题人和计算机都能完成,第1课计算机与计算思维.ppt
  2. 转载 CreateWaitableTimer和SetWaitableTimer函数
  3. Cooike的一些用法
  4. php数组转字符串 join,jQuery中将数组转换成字符串join()和push()使用
  5. webkit webApp 开发技术要点总结
  6. Google 最新的 Fuchsia OS【科技讯息摘要】
  7. 计算机硬件性能关联性,计算机硬件系统组成课堂教学有效研究结题报告.ppt
  8. 小米MIX 4内部代号曝光:“众神之王”三季度降临
  9. 【Vue2.0】— 消息订阅与发布pubsub(二十)
  10. Nodejs中的this详解
  11. Nautilus获得了标签化支持
  12. MIND新闻推荐冠军分享细节揭秘
  13. 关于IOC反射错误(无法加载一个或多个请求的类型。有关更多信息,请检索 LoaderExceptions 属性...)的诊断办法...
  14. 21天学通Java学习笔记-Day03
  15. autocad 如何摆正显示_AutoCAD使用技巧问答
  16. 未来房价涨or跌?大数据告诉你
  17. Oracle导出FSG,SQL语句 - FSG行集、列集定义导出
  18. 如何用matlab对两个行向量作图_matlab 绘图与图形处理(二)
  19. 生活已经离不开网络,家用路由器与工业路由器有哪些区别,必备知识看完就明白
  20. UR机械臂学习(7-1):MoveIt简单编程实现机械臂运动(正逆运动学)

热门文章

  1. oracle树子类遍历父类_不懂数据库索引的底层原理?那是因为你心里没点b树
  2. python使用joblib多进程执行for循环
  3. python中文人名识别(使用hanlp,LTP,LAC)
  4. series、dataframe转为tensor格式数据
  5. 程序—java记事本
  6. java8之list集合中取出某一属性的方法
  7. Centos7下安装python3
  8. 一些关于Spring的随笔
  9. juc原子类之五:AtomicLongFieldUpdater原子类
  10. magento导入导出Custom Options, Tier Prices and Grouped Products