C++ 定位构造
看起来不可能的事情在C++中总能找到解决的办法。
正如,直接调用构造函数是不可能的,然而可以用placement new 欺骗编译器调用构造函数。
--------------------------------------------------
1、placement new 为何物?
placement new 是重载operator new 的一个标准、全局的版本,它不能够被自定义的版本代替(不像普通版本的operator new 和 operator delete能够被替换)。
|
1
2
|
void *operator new( size_t, void *p ) throw() { return p; } |
placement new的执行忽略了size_t参数,只返还第二个参数。其结果是允许用户把一个对象放到一个特定的地方,达到调用构造函数的效果。
|
1
2
3
4
5
6
|
class SPort { ... }; // represents a serial portconst int comLoc = 0x00400000; // location of a port//...void *comAddr = reinterpret_cast<void *>(comLoc);SPort *com1 = new (comAddr) SPort; // create object at comLoccom1->~SPort(); //释放 |
2、new 、operator new 和 placement new 一样吗?
new :不能被重载,其行为总是一致的。它先调用operator new分配内存,然后调用构造函数初始化那段内存。
operator new:要实现不同的内存分配行为,应该重载operator new,而不是new。
delete和operator delete类似。
placement new:只是operator new重载的一个版本。它并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能删除它,但需要调用对象的析构函数。
3、在已有的内存上用placement new分配数组
|
1
2
3
4
5
6
|
const int numComs = 4;//...SPort *comPorts = new (comAddr) SPort[numComs]; // create arrayint i = numComs;while( i ) comPorts[--i].~SPort(); |
4、用Placement new 解决buffer的问题
用new分配的数组缓冲时,由于调用了默认构造函数,因此执行效率上不佳。若没有默认构造函数则会发生编译时错误。用Placement new可以解决此类问题。
|
1
2
3
4
5
6
7
8
9
10
11
12
|
const size_t n = sizeof(string) * BUFSIZE;string *sbuf = static_cast<string *>(::operator new( n ));int size = 0;//此时,buffer还没有初始化,因此需要用 placement new 调用copy构造函数初始化。void append( string buf[], int &size, const string &val ) { new (&buf[size++]) string( val ); } // placement new//最后的清理void cleanupBuf( string buf[], int size ) { while( size ) buf[--size].~string(); // destroy initialized elements ::operator delete( buf ); // free storage} |
-----------------------------------------------------------------------------------------------------------------------------------
在处理内存分配的时候,C++程序员会用new操作符(operator new)来分配内存,并用delete操作符(operator delete)来释放内存。这是一个new操作符的例子。
|
1
2
3
4
5
6
7
8
|
class CTest{};// . . . 代码// 分配一个对象CTest * pTest = new Test;// 分配一个有十个对象的数组 (CTest 要有缺省构造函数(default constuctor))CTest * p10Tests = new Test[ 10]; |
虽然这种写法在大多数时候都工作得很好,但还是有些情况下使用new是很烦人的,比如当你想重新分配一个数组或者当你想在预分配的内存上构造一个对象的时候。
比如第一种情况,重新分配一个数组效率是很低的:
|
1
2
3
4
5
6
7
8
9
10
|
// 分配一个有10个对象的数组CTest * pTests = new Test[ 10];// . . .// 假设现在我们需要11个对象CTest * pNewTests = new Test[ 11];// . . . 我们必须把原来的对象拷贝到新分配的内存中for ( int i = 0; i < 10; i++)pNewTests[ i] = pTests[ i];delete pTests;pTests = pNewTests; |
如果你想在预分配的内存上创建对象,用缺省的new操作符是行不通的。要解决这个问题,你可以用placement new构造。它允许你构造一个新对象到预分配的内存上:
// buffer 是一个void指针 (void *)
// 用方括号[] 括起来的部分是可选的
[CYourClass * pValue = ] new( buffer) CYourClass[( parameters)];
下面是一些例子:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#include <new>class CTest{public:CTest(){}CTest( int){}};int main(int argc, char* argv[]){// 由于这个例子的目的,我们不考虑内存对齐问题char strBuff[ sizeof( CTest) * 10 + 100];CTest * pBuffer = ( CTest *)strBuff;// 缺省构造CTest * pFirst = new(pBuffer) CTest;// 缺省构造CTest * pSecond = new(pBuffer + 1) CTest;// 带参数的构造;// 不理会返回的指针new(pBuffer + 2) CTest( 5);// 带参数的构造CTest * pFourth = new( pBuffer + 3) CTest( 10);// 缺省构造CTest * pFifth = new(pBuffer + 4) CTest();// 构造多个元素(缺省构造)CTest * pMultipleElements = new(pBuffer + 5) CTest[ 5];return 0;} |
当你有自己的内存缓冲区或者在你实现自己的内存分配策略的时候,placement new会很有用。事实上在STL中广泛使用了placement new来给容器分配内存;每个容器类都有一个模版参数说明了构造/析构对象时所用的分配器(allocator)。
在使用placement new的时候,你要记住以下几点:加上头文件#include <new>
你可以用placement new构造一个数组中的元素。要析构一个用placement new分配的对象,你应该手工调用析构函数(并不存在一个“placement delete”)。它的语法如下:
|
1
2
|
pFirst->~CTest();pSecond->~CTest(); |
// . . . 等等
----------------------------------------------------------------------------------
|
1
2
3
4
5
6
7
8
|
void someCode(){ char memory[sizeof(Fred)]; void* p = memory; Fred* f = new(p) Fred();... f->~Fred(); // Explicitly call the destructor for the placed object} |
--------------------------------------------------------------------------------
【注】很浅显的关于placement new的文章,入门读读,不错!
placement new 是重载operator new的一个标准、全局的版本,它不能被自定义的版本代替(不像普通的operator new和operator delete能够被替换成用户自定义的版本)。
它的原型如下:
|
1
|
<tt style="color:#454545;font-size:14px;line-height:21px;background-color:#ffffff;">void *operator new( size_t, void *p ) throw() { return p; }</tt> |
首先我们区分下几个容易混淆的关键词:new、operator new、placement new
new和delete操作符我们应该都用过,它们是对堆中的内存进行申请和释放,而这两个都是不能被重载的。要实现不同的内存分配行为,需要重载operator new,而不是new和delete。
看如下代码:
|
1
2
|
<tt style="color:#454545;font-size:14px;line-height:21px;background-color:#ffffff;">class MyClass {…};MyClass * p=new MyClass;</tt> |
这里的new实际上是执行如下3个过程:
1. 调用operator new分配内存 ;2. 调用构造函数生成类对象;3. 返回相应指针。
operator
new就像operator+一样,是可以重载的。如果类中没有重载operator new,那么调用的就是全局的::operator
new来完成堆的分配。同理,operator new[]、operator delete、operator
delete[]也是可以重载的,一般你重载的其中一个,那么最后把其余的三个都重载一遍。
至
于placement new才是本文的重点。其实它也只是operator
new的一个重载的版本,只是我们很少用到它。如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placement
new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void*p实际上就是指向一个已经分配好的内存缓冲区的的首地址。
我
们知道使用new操作符分配内存需要在堆中查找足够大的剩余空间,这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。
placement
new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途
出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。
使用方法如下:
1. 缓冲区提前分配
可以使用堆的空间,也可以使用栈的空间,所以分配方式有如下两种:
|
1
2
|
<tt style="color:#454545;font-size:14px;line-height:21px;background-color:#ffffff;">class MyClass {…};char *buf=new char[N*sizeof(MyClass)+sizeof(int)];或者char buf[N*sizeof(MyClass)+sizeof(int)];</tt> |
2. 对象的构造
|
1
|
<tt style="color:#454545;font-size:14px;line-height:21px;background-color:#ffffff;">MyClass * pClass=new(buf) MyClass;</tt> |
3. 对象的销毁
一旦这个对象使用完毕,你必须显式的调用类的析构函数进行销毁对象。但此时内存空间不会被释放,以便其他的对象的构造。
|
1
|
<tt style="color:#454545;font-size:14px;line-height:21px;background-color:#ffffff;">pClass->~MyClass();</tt> |
4. 内存的释放
如果缓冲区在堆中,那么调用delete[] buf;进行内存的释放;如果在栈中,那么在其作用域内有效,跳出作用域,内存自动释放。
注意:
在
C++标准中,对于placement operator new []有如下的说明: placement operator new[] needs
implementation-defined amount of additional storage to save a size of
array. 所以我们必须申请比原始对象大小多出sizeof(int)个字节来存放对象的个数,或者说数组的大小。
使用方法第二步中的new才是placement new,其实是没有申请内存的,只是调用了构造函数,返回一个指向已经分配好的内存的一个指针,所以对象销毁的时候不需要调用delete释放空间,但必须调用析构函数销毁对象。
reference:http://hi.baidu.com/gamedot/item/05c8c831ee1a2624b2c0c5a1
C++ 定位构造的更多相关文章
- C++ new(3)
转载自:http://www.builder.com.cn/2008/0104/696370.shtml “new”是C++的一个关键字,同时也是操作符.关于new的话题非常多,因为它确实比较复杂,也 ...
- [GeekBand] C++学习笔记(2)——BigThree、OOP
本篇笔记主要分为三个部分,第一部分是以String类为例的基于对象的编程,重点在于构造与析构.拷贝构造函数.拷贝赋值函数三个重要函数.这一部分与笔记(1)中的内容结合起来就是基于对象编程的主要内容.第 ...
- 侯捷STL课程及源码剖析学习2: allocator
以STL 的运用角度而言,空间配置器是最不需要介绍的东西,它总是隐藏在一切组件(更具体地说是指容器,container)的背后,默默工作默默付出. 一.分配器测试 测试代码 #include < ...
- this指针 new 和delete
指针类型的函数:函数的返回值是指针. 不要将非静态局部地址用作函数的返回值,离开函数后就失效了 在子函数中定义局部变量后将其地址返回给函数就是非法地址 在子函数中用new操作取得的内存地址返回给主函数 ...
- new的三种形态
C++语言一直被认为是复杂编程语言中的杰出代表之一,不仅仅是因为其繁缛的语法规则,还因为其晦涩的术语.下面要讲的就是你的老熟人—new: 它是一个内存管理的操作符,能够从堆中划分一块区域,自动调用构造 ...
- C++:new&delete
一.new的浅析 在C++中,new主要由三种形式:new operator.operator new和placement new • new operator new operator即一些C++书 ...
- 深入C++的new
new”是C++的一个关键字,同时也是操作符.关于new的话题非常多,因为它确实比较复杂,也非常神秘,下面我将把我了解到的与new有关的内容做一个总结. new的过程 当我们使用关键字new在堆上动态 ...
- C++ new操作符详解
一.new操作符的概念 我们通常讲的new是指的是new operator,其实还有另外两个概念,operator new 和 placement new. 1.new operator 我们在使用n ...
- ZT C++关键字new学习
http://blog.csdn.net/waken_ma/article/details/4007914 C++关键字new学习 很多新手对C++关键字new可能不是很了解吧,今天我一起来学习一下. ...
随机推荐
- word操作
- 常见 WEB 安全漏洞(转)
SQL注入 成因:程序未对用户的输入的内容进行过滤,从而直接代入数据库查询,所以导致了sql 注入 漏洞 . 思路:在URL处可以通过 单引号 和 and 1=1 and 1=2 等语句进行手工测试s ...
- Netty聊天室-源码
目录 Netty聊天室 源码工程 写在前面 [百万级流量 聊天室实战]: [分布式 聊天室] [Spring +Netty]: [Netty 原理] 死磕 系列 [提升篇]: [内力大增篇]: 疯狂创 ...
- TVirtualStringTree的Minimal例子学习
预步骤第一步,定义数据结构type PMyRec = ^TMyRec; TMyRec = record Caption: WideString; end;预步骤第二步,规定取得节点数据时候的大小pro ...
- JavaScript学习笔记(持续更新)
函数有“定义式”和“变量式”两种写法的区别 函数有“定义式”和“变量式”两种写法,两者基本区别不大.主要区别在于,如果在同一个代码块(Script标签对)定义两个同名的函数,浏览器在预编译代码时,使用 ...
- API的理解和使用——单线程架构
核心知识点: 1.单线程机制:所有命令放在一个队列中 2.为什么Redis单线程这么快?内存中执行.非IO阻塞.避免线程切换和竞态产生的消耗. 3.单线程的问题?一个命令不能执行太长时间,不然会阻塞其 ...
- gradle 与 gradlew
配置好gradle环境变量后(配置环境变量这里就不说了,可以自行百度),我们就可以在cmd中使用gradle命令了 在cmd中输入: gradle -v 输出如下: 这时说明gradle的环境变量配置 ...
- 模型层的Meta选项详解
一 . 模型层的Meta选项详解 Django模型类的Meta是一个内部类,它用于定义一些Django模型类的行为特性.便用方法及参数解释如下 : class Book(models.Model): ...
- PAT 乙级 1081. 检查密码 (15) 【字符串】
题目链接 https://www.patest.cn/contests/pat-b-practise/1081 思路 有一个坑点 可能会输入空格 也就是说 要用 geline 或者 gets() 然后 ...
- Too many open files解决方案及原理
以下是我解决Too many open files异常时学习的知识的理解和总结,如有不正确指出,敬请指出! 此问题中文搜索雷同,你可以尝试以下关键字:"file descriptor lea ...