placement new–《C++必知必会》 条款35

placement new是重载operator new的一个标准、全局的版本,它不能被自定义的版本代替(不像普通的operator new和operator delete能够被替换成用户自定义的版本)。

placement new原型为:

void * operator new( size_t , void * p) throw(){return n;}

operator new的原型为:

void * operator new( size_t t) { return n;}

首先我们区分下几个容易混淆的关键词:new、operator new、placement new

new和delete操作符我们应该都用过,它们是对堆中的内存进行申请和释放,而这两个都是不能被重载的。要实现不同的内存分配行为,需要重载operator new,而不是new和delete。

看如下代码:

class MyClass {…};

MyClass * p=new MyClass;

这里的new实际上是执行如下3个过程:

1调用operator new分配内存;

2调用构造函数生成类对象;

3返回相应指针。

operator new就像operator+一样,是可以重载的,但是不能在全局对原型为void *  operator new(size_t size) 这个原型进行重载,一般只能在类中进行重载。如果类中没有重载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. 缓冲区提前分配

可以使用堆的空间,也可以使用栈的空间,所以分配方式有如下两种:

class MyClass {…};
char *buf=new char[N*sizeof(MyClass)+ sizeof(int) ] ; 或者char buf[N*sizeof(MyClass)+ sizeof(int) ];

  1. 对象的构造

MyClass * pClass=new(buf) MyClass;

  1. 对象的销毁

一旦这个对象使用完毕,你必须显式的调用类的析构函数进行销毁对象。但此时内存空间不会被释放,以便其他的对象的构造。

pClass->~MyClass();

  1. 内存的释放

如果缓冲区在堆中,那么调用delete[] buf;进行内存的释放;如果在栈中,那么在其作用域内有效,跳出作用域,内存自动释放。

注意:

1) 在C++标准中,对于placement operator new []有如下的说明: placement operator
new[] needs implementation-defined amount of additional storage to save a
size of array. 所以我们必须申请比原始对象大小多出sizeof(int)个字节来存放对象的个数,或者说数组的大小。

2) 使用方法第二步中的new才是placement new,其实是没有申请内存的,只是调用了构造函数,返回一个指向已经分配好的内存的一个指针,所以对象销毁的时候不需要调用delete释放空间,但必须调用析构函数销毁对象。

 

new的时候,其实做了两件事,一是:调用malloc分配所需内存,二是:调用构造函数。

delete的时候,也是做了两件事,一是:调用析造函数,二是:调用free释放内存。

 

一个例子代码:placement new 在VC6.0不支持,在Qt支持

class SPort{
public:
    ~SPort(){}
};
const int comLoc=0x00400000;//一个串口空间的位置


//... void * comAddr = reinterpret_cast<void *>(comLoc); SPort * com1 = new (comAddr) SPort;//在comLoc位置创建对象 //... com1->~SPort(); //comLoc变量,离开作用域自动销毁 /* void * operator new( size_t , void *p) throw() 而言,一下代码 SPort * com1 = new (comAddr) SPort; size_t 参数被初始化为 SPort对象的大小(以字节为单位),void * 类型的参数P 则以 comAddr 为处值。 */ //placement new 也可以在给定空间位置创建对象数组 const int numComs=4; //... SPort * comPorts = new(comAddr) SPort[numComs];//创建数组 //这些数组元素必须销毁 int i = numComs; while(i) { comPorts[--i].~SPort(); }

第二个代码例子:(我没懂2018.3.22)

/*
    对象数组可能出现问题的地方在于:当数组被分配时,每一个元素必须通过调用一个默认构造函数而被初始化。
    下面:考虑一个简单的、固定大小的缓冲区,可以想向这个缓存区append(附加)值。
*/

#define BUFSIZE 1000
std::string *sbuf = new std::string[BUFSIZE]; //调用默认构造函数
int size = 0;
void append(std::string buf[],int &size , const std::string &val)
{
    buf[size++] = val;   //刚才的默认构造函数白做了!  //这里我没懂
}
/*
    如果数组只有一部分元素被使用,或者元素被立即赋值,那么以上的做法效率低下。更糟糕的是,如果数组的元素类型没有默认的构造函数,
    我们将的到一个编译器错误。
    placement new 通常用于解决此类缓冲区问题。采用这种方式,缓冲区占用的存储区的分配,可以避免被默认的构造函数初始化
    (如果默认构造函数包含初始化代码)
*/
const size_t n = sizeof(std::string) * BUFSIZE;
std::string * sbuf_ = static_cast<std::string *>(::operator new(n)); //不是太清楚
int size_ = 0;
/*
在第一次访问数组元素时,不能为其赋值,因为它还没有被初始化。可以使用placement new 通过赋值构造函数来初始化元素。
*/
void append_(std::string buf[], int & size ,const std::string &val)
{
    new(&buf[size++]) std::string(val) ;
}
/*
    通常,使用placement new 也需要做一些清理工作
*/
void cleanupBuf(std::string buf[],int size)
{
    while (size)
        buf[--size].~basic_string(); //qt下测试是~basic_string  //原本buf[--size].~string();
    ::operator delete(buf);
}
/*
    这种方式快速而灵活,它并不指望被普通大众所知晓。这一基本技术(以更高级的形式)广泛的应用于大多数标准容器的实现
*/

 

 

2

原文链接: https://www.cnblogs.com/azbane/p/8625439.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    placement new--《C++必知必会》 条款35

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/398997

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月12日 上午9:55
下一篇 2023年4月12日 上午9:55

相关推荐