C++11特性总结(5)-allocator类和placement new

836 阅读4分钟

allocator类

头文件:<memory>

作用:可以做到分配内存和对象构造分离。意味着分配的时候不用调用构造函数。可以在使用对象的时候再按需构造,省去了构造对象的时间,更加方便、高效。

相比之下,用new分配对象空间的时候,我们知道new会自动调用对象的构造函数,但是有时候我们在为对象开辟空间的时候,其实还没有准备好为对象赋值。

string *p = new string[n]  //开辟空间并构造了n个空字符串对象
for(int i = 0; i < n; ++i)
{
    cin>>p[i]; //为n个字符串对象赋值
}

在上例程序中,可以看到,用new来分配对象空间,调用构造函数为我们初始化了空字符串,这对我们来说并没有意义,因为我们知道后面通过赋值会覆盖原有的初始化值。

我们来看看allocator类将怎么处理:

allocator使用方法:

1.声明allocator类:
allocator<string> a;

2.分配未构造的空间:
string* p = a.allocate(n)  //n个string 类型的原始空间,返回指向空间的指针

3.构造对象:
for(int i=0;i<n;i++)
{    
    string args;
    cin>>args;
    a.construct(p+i,args)  //对每一个对象进行初始化,args是对应的构造函数参数,p必须是对应的allocate分配的指针
}
4.析构对象:
for(int i=0;i<n;i++)
    a.destroy(p+i)
    
5.释放空间:
a.deallocate(p,n)  //p是指针,n是对象个数

可以看到,通过适时的构造对象,我们无需通过赋值操作覆盖原对象的初始值,减少了不必要的操作。

注意事项:

  1. 使用allocator分配的内存,必须用construct构造对象才能使用,否则该行为未定义
  2. construct和destroy要一一对应,构造和析构配套
  3. deallocate的参数指针不得为空,且必须指向由allocator分配的内存;n对象个数必须与分配的对象个数一致(意味着我们不能分批释放资源)

placement new

placement new不是C++11才有的运算符。再此提及是因为和allocator功能较为相似

作用:用来在一个指定的内存地址上构造对象

用法:

1.提前分配空间
char *buf  = new char[sizeof(string)]; // 在堆上分配空间,也可以在栈上分配空间:char buf[sizeof(string)];  

2.使用operator new构造对象
string *p = new (buf) string("hi");    // placement new

3.析构对象
p->~string(); //显式调用析构函数

4.释放空间
delete []buf  

placement new的使用较为自由,我们可以选择多种方式初始化原始空间,可以在栈上也可以在堆上分配空间。一般都是以char类型申请空间(因为char类型占1个字节)。需要注意的是:这里为char分配空间,使用了new,为什么我们还说是原始空间?因为基本数据类型没有构造对象的概念,它无需构造函数。

placement new使用的空间是可以反复利用的,在第三步析构对象之后,实际上我们可以使用buf指针指向的空间构造其他的对象

两者比较

总的来说,两者都能够达到分配内存和对象构造分离的目的,有一些区别如下:

  1. placement new是运算符,allocator是C++11提供的标准库类模板。
  2. placement new其实是承担了构造函数的任务,allocator是拥有分配空间、构造对象、析构对象、释放空间 一整套服务。
  3. placement new可以灵活构造不同的类型对象,allocator在一开始定义的时候必须提供一个确定的类型参数才能初始化allocator类,之后无法改变。
  4. placement new的使用场景:一般是为了在重点程序部分,减去分配空间的开销(使用new操作符分配内存需要在堆中查找足够大的剩余空间),并且提高部分代码的可靠性(不需要担心内存分配失败)
  5. allocator的使用场景:一般绝大多数情况可以被placement new代替。