重新认识new
前言
感谢大佬:https://www.cnblogs.com/luxiaoxun/archive/2012/08/10/2631812.html
因为这段时间在重新再次学习STL,在学习到deque时,遇到了allocator类,学习过程中又遇到operator new,发现现在我认识的new,已经不是我之间认识的那个new了,故我要好好认清她。
之前从C转向C++,因此免不了的就是malloc / free 和 new / delete对比。
1. malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
2. 对于内部数据类型(int, char, double...)的对象而言,光用malloc / free已经可以满足动态对象的要求。而在非内部数据类型(即类)对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数,这时候malloc / free已经无法满足,这时候new / delete应允而生。
而在面试中也会经常被问到,但是现在来看我了解还是皮毛。。。
如果读到这的话,写的可能比较冗余 请耐心看下去 谢谢!!!
new operator(delete operator) & operator new (operator delete)
首先我们得弄清楚 new operator(delete operator) & operator new (operator delete)它们不一样,不一样。。。
new operator (delete operator) 就是new (delete)操作符,而operator new (operator delete)是函数。
new operator
(1)调用operator new分配足够的空间,并调用相关对象的构造函数
(2)不可以被重载
operator new
(1)只分配所要求的空间,不调用相关对象的构造函数。当无法满足所要求分配的空间时,则
->如果有new_handler,则调用new_handler,否则
->如果没要求不抛出异常(以nothrow参数表达),则执行bad_alloc异常,否则
->返回null pointer
(2)可以被重载
(3)重载时,返回类型必须声明为void*
(4)重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t
(5)重载时,可以带其它参数
operator new 函数
请先注意operator new的version (1)、(2),对于version(3)后一节会单独拎出
函数原型:
. void* operator new(std::size_t size);
//分配size字节存储空间,返回指向新分配的存储空间的指针,失败抛出一个bad_alloc异常
.void* operator new(std::size_t size, const std::nothrow_t& nothrow_value);
//与第一个相同,只是在内存分配失败时,不会抛出异常,而是返回一个null指针
对于nothrow的相关东西,请移步于我的另一篇博文
http://www.cnblogs.com/SimonKly/p/7826660.html
.void* operator new(std::size_t size, void* ptr) throw(); //placement new
//简单返回ptr,不会分配空间
这个版本的operator new重载方式,不同于前两个版本,而是单独拿出来的,被叫做placement new。是最难理解(我比较笨,琢磨了好久)
默认的分配和释放函数是标准库中特殊组件,有以下性质:
* 全局性: operator new三个版本都在全局命名空间声明
* 隐式声明: 无论是否包含头<new>,分配版本((1)和(2))在C++程序的每个翻译单元中隐式声明。
* 可重载: 分配版本((1)和(2))也是可替换的:程序可以提供自己的定义来替换默认提供的定义来产生上述结果,或者可以为特定类型重载。
下面有一个 http://www.cplusplus.com 例子,虽然很简单,但是对于理解我认为有很好的帮助
#include <iostream>
#include <new> using namespace std; class Simon
{
public:
Simon()
{
cout << "constructed [ " << this << " ]" << endl;
} private:
int s;
}; int main(int argc, char** argv)
{
cout << "1: ";
Simon* s1 = new Simon;
// 分配内存调用 operator new(sizeof(Simon));
//然后构建对象 调用构造函数 cout << "2: ";
Simon* s2 = new(std::nothrow) Simon;
// 分配内存调用 operator new(sizeof(Simon), std::nothrow);
// 然后构建对象 调用构造函数 cout << "3: ";
new(s2) Simon;
// 不分配内存调用 operator new(sizeof(Simon), s2);
// 在s2的地址空间构建对象(可以从输出结果看出),调用构造函数 cout << "4: ";
Simon* s3 = (Simon*)operator new(sizeof(Simon));
// 只分配内存
// 不构建对象 delete s1;
delete s2;
delete s3; cout << endl;
system("pause");
return ;
}
运行结果:
总结:可以看出version (1)、(2)只是分配存储空间,而不构建对象 ,new operator分配空间时,调用version(1)or (2)来进行自定义化内存分配
Notice:
operator new可以显式调用为常规函数,但在C++中,new是具有非常特定行为的运算符:具有new运算符的表达式首先调用具有其类型说明符大小的函数operator new() 作为第一个参数,如果这是成功的,它会自动初始化或构造对象(如果需要的话)。
为什么有必要写自己的operator new和operator delete?why?(其实我也不知道,,,)
答案通常是:为了效率。缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也使得在某些特定的场合下,可以进一步改善它的性能。尤其在那些需要动态分配大量的但很小的对象的应用程序里,情况更是如此。具体可参考《Effective C++》中的第二章内存管理。
placement new
placement new其实就是operator new重载第三个版本,也不知道谁为什么叫其placement new,害的我还以为是一个新东西,之后发现就是 http://www.cplusplus.com (见下图)上,曰其为 placement 版本。。。
void* operator new(size_t size, void* p) throw()
{
return p;
}
placement new的执行忽视size_t参数,只返回还第二个参数。允许用户把一个对象放到一个指定的内存缓冲器中,参数p它就指向一个内存缓冲器。
placement new使用步骤
placement new主要适用于:
在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定的;长时间运行而不被打断的程序,以及执行一个垃圾收集器(GC, garbage collector)
在很多情况下,placement new的使用方法和其他普通的new有所不同。这里提供了它的使用步骤。
第一步 缓存提前分配
有三种方式:
1.为了保证通过placement new使用的缓存区的memory alignment(内存队列)正确准备,使用普通的new来分配它:在堆上进行分配
class Task ;
char * buff = new [sizeof(Task)]; //分配内存
(请注意auto或者static内存并非都正确地为每一个对象类型排列,所以,你将不能以placement new使用它们。)
2.在栈上进行分配
class Task ;
char buf[N*sizeof(Task)]; //分配内存
3.还有一种方式,就是直接通过地址来使用。(必须是有意义的地址)
void* buf = reinterpret_cast<void*> (0xF00F);
第二步:对象的分配
在刚才已分配的缓存区调用placement new来构造一个对象。
Task *ptask = new (buf) Task
第三步:使用
按照普通方式使用分配的对象:
ptask->memberfunction();
ptask-> member;
//...
第四步:对象的析构
一旦你使用完这个对象,你必须调用它的析构函数来销毁它。按照下面的方式调用析构函数:
ptask->~Task(); //调用外在的析构函数
这里是不是就像GC机制呢?
你可以反复利用缓存并给它分配一个新的对象(重复步骤2,3,4),节省了申请空间的时间开销。
第五步:释放
如果你不打算再次使用这个缓存,你可以象这样释放它:delete [] buf;
跳过任何步骤就可能导致运行时间的崩溃,内存泄露,以及其它的意想不到的情况。如果你确实需要使用placement new,请认真遵循以上的步骤。
如下有一个简单程序:
#include <iostream> using namespace std; class animal
{
public:
#if 1 //用于演示,无默认构造函数
animal() : num()
{
cout << "animal constructor default" << endl;
}
#endif
animal(int _num) : num(_num)
{
cout << "animal constructor param" << endl;
} ~animal()
{
cout << "animal destructor" << endl;
} void show()
{
cout << this->num << endl;
} void * operator new(size_t size, void *p)
{
return p;
} private:
int num;
}; int main(int args, char ** argv)
{
// 一个动态animal数组
void *p = operator new( * sizeof(animal)); // 申请缓冲器
animal *a = static_cast<animal *>(p); // 转换类型 // 2.对象构建
for (int i = ; i < ; i++)
{
new(a + i) animal(i);// 调用重载构造
}
new(a + ) animal; // 也可以调用默认构造 // 3.使用
for (int i = ; i < ; i++)
{
(a + i)->show();
} // 4.销毁对象
for (int i = ; i < ; i++)
{
(a + i)->~animal();
} // 5.回收空间
delete[]p; cin.get();
return ;
}
运行结果:
placement new 存在的理由
1.用placement new 解决buffer的问题
问题描述:用new分配的数组缓冲时,由于调用了默认构造函数,因此执行效率上不佳。若没有默认构造函数则会发生编译时错误。如果你想在预分配的内存上创建对象,用缺省的new操作符是行不通的。要解决这个问题,你可以用placement new构造。它允许你构造一个新对象到预分配的内存上。
2.增大时空效率的问题
使用new操作符分配内存需要在堆中查找足够大的剩余空间,显然这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。
new 、operator new 和 placement new 区别
(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[]也是可以重载的。
(3)placement new:只是operator new重载的一个版本。它并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能删除它,但需要调用对象的析构函数。
如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void* p实际上就是指向一个已经分配好的内存缓冲区的的首地址
随机推荐
- vc code 一个非常不错的插件
https://marketplace.visualstudio.com/items?itemName=CoenraadS.bracket-pair-colorizer 这个是地址,前提下是安装了vc ...
- 使用requests_html抓取数据
from requests_html import HTMLSession import json class YejiCollege: def __init__(self, url): self.u ...
- Python入门习题7.分别统计输入各类字符个数
例7.用户从键盘输入一行字符,编写一个程序,统计并输出其中的英文字符(包括中文字符).数字.空格和其他字符个数. #字符数统计.py Str = input('请输入一行字符:') alpha = 0 ...
- jvm性能监控(5)-jdk自带工具 VisualVM
一.在服务器的jdk的bin目录下添加配置文件 jstatd.all.policy [root@localhost /]# cd /usr/local/src/jdk1.8.0_131/bin/ [r ...
- python学习第十五天集合的创建和基本操作方法
集合是python独有的数据列表,集合可以做数据分析,集合是一个无序的,唯一的的数据类型,可以确定列表的唯一性,说一下集合的创建和基本常见操作方法 1,集合的创建 s={1,2,4} 也可以用set( ...
- POJ 1438 One-way Traffic (混合图+边双连通)
<题目链接> 题目大意: 给定一个混合图,问你在能够使得图中所有点能够两两到达的情况下,尽可能多的将无向边变成有向边,输出这些无向边的变化方案. 解题分析:这与之前做过的这道题非常类似 P ...
- 故事版(storyBoard)-lllegal configuration connection cannot have a prototype objct as
今天下午做项目的时候.居然出了一个太不是问题的问题了,这个错误太低级了. lllegal configuration connection 'flagImg' cannot have a protot ...
- google+ sign in and get the oauth token 转摘:https://gist.github.com/ianbarber/5170508
package com.example.anothersignintest; import java.io.IOException; import com.google.android.gms ...
- CSS中的关系选择器
关系选择器是指根据与其他元素的关系选择元素的选择器,常见的符号有空格.>.~,还 有+等,这些都是非常常用的选择器. 后代选择器:选择所有合乎规则的后代元素.空格连接. 相邻后代选择器:仅仅选择 ...
- 2019-1-25-WPF-ListBox-的选择
title author date CreateTime categories WPF ListBox 的选择 lindexi 2019-01-25 21:43:17 +0800 2018-2-13 ...