new Placement是C++中的new变体运算符。普通的new运算符有两件事:(1)分配内存(2)在分配的内存中构造一个对象。
新的展示位置使我们可以将上述两点分开。在new放置中,我们可以传递一个预分配的内存,并在所传递的内存中构造一个对象。
新版vs展示位置新版
- 普通new分配堆中的内存并构造对象,而使用new放置可以在已知地址完成对象构造。
- 使用普通new时,不知道它指向的地址或存储位置,而使用new放置时却知道其指向的地址或存储位置。
- 当分配由new完成但没有放置删除时,使用delete操作完成取消分配,但是如果需要,可以在析构函数的帮助下将其写入
new (address) (type) initializer
As we can see, we can specify an address
where we want a new object of given type
to be constructed.
什么时候更喜欢使用new展示位置?
由于它允许在已经分配的内存上构造一个对象,因此优化是必需的,因为这样可以更快地避免一直重新分配。在某些情况下,可能需要多次重建对象,因此,在这些情况下放置新的运算符可能会更有效。
// C++ program to illustrate the placement new operator
#include
using namespace std;
int main()
{
// buffer on stack
unsigned char buf[sizeof(int)*2] ;
// placement new in buf
int *pInt = new (buf) int(3);
int *qInt = new (buf + sizeof (int)) int(5);
int *pBuf = (int*)(buf+0) ;
int *qBuf = (int*) (buf + sizeof(int));
cout << "Buff Addr Int Addr" << endl;
cout << pBuf <<" " << pInt << endl;
cout << qBuf <<" " << qInt << endl;
cout << "------------------------------" << endl;
cout << "1st Int 2nd Int" << endl;
cout << *pBuf << " "
<< *qBuf << endl;
return 0;
}
输出:
Buff Addr Int Addr
0x69fed8 0x69fed8
0x69fedc 0x69fedc
------------------------------
1st Int 2nd Int
3 5
下图以图形方式显示了以上C++程序中实际发生的情况。
下面是C++中的另一个简单实现,以说明C++中new的放置方式:
// C++ program to illustrate the placement new operator
#include
using namespace std;
int main()
{
// initial value of X
int X = 10;
cout << "Before placement new :" << endl;
cout << "X : " << X << endl;
cout << "&X : " << &X << endl;
// Placement new changes the value of X to 100
int *mem = new (&X) int(100);
cout << "\nAfter placement new :" << endl;
cout << "X : " << X << endl;
cout << "mem : " << mem << endl;
cout << "&X : " << &X << endl;
return 0;
}
输出:
Before placement new :
X : 10
&X : 0x69fee8
After placement new :
X : 100
mem : 0x69fee8
&X : 0x69fee8
说明:在这里,很明显,借助于放置new运算符在x的地址处分配了x的新值。通过&X和mem的值相等这一事实可以清楚地看出这一点。
下图以图形方式显示了以上C++程序中实际发生的情况。
如何删除由place new分配的内存?
运算符delete只能删除在堆中创建的存储,因此当使用placement new时,delete运算符不能用于删除该存储。在使用placement new运算符进行内存分配的情况下,由于它是在堆栈中创建的,因此编译器知道何时删除它,它将自动处理内存的重新分配。如果需要,可以在析构函数的帮助下编写它,如下所示。
// C++ program to illustrate using destructor for
// deleting memory allocated by placement new
#include
#include
#include
using namespace std;
class Complex
{
private:
double re_, im_;
public:
// Constructor
Complex(double re = 0, double im = 0): re_(re), im_(im)
{
cout << "Constructor : (" << re_
<< ", " << im_ << ")" << endl;
}
// Destructor
~Complex()
{
cout << "Destructor : (" << re_ << ", "
<< im_ << ")" << endl;
}
double normal()
{
return sqrt(re_*re_ + im_*im_);
}
void print()
{
cout << "|" << re_ <<" +j" << im_
<< " | = " << normal() << endl;
}
};
// Driver code
int main()
{
// buffer on stack
unsigned char buf[100];
Complex* pc = new Complex(4.2, 5.3);
Complex* pd = new Complex[2];
// using placement new
Complex *pe = new (buf) Complex(2.6, 3.9);
// use objects
pc -> print();
pd[0].print();
pd[1].print();
pe->print();
// Release objects
// calls destructor and then release memory
delete pc;
// Calls the destructor for object pd[0]
// and then release memory
// and it does same for pd[1]
delete [] pd;
// No delete : Explicit call to Destructor.
pe->~Complex();
return 0;
}
输出:
Constructor : (4.2, 5.3)
Constructor : (0, 0)
Constructor : (0, 0)
Constructor : (2.6, 3.9)
|4.2 +j5.3 | = 6.7624
|0 +j0 | = 0
|0 +j0 | = 0
|2.6 +j3.9 | = 4.68722
Destructor : (4.2, 5.3)
Destructor : (0, 0)
Destructor : (0, 0)
Destructor : (2.6, 3.9)
说明:析构函数在这里被显式调用,因为在这里不能将其打包在delete运算符,因为delete将需要释放您在此处没有的内存,并且它不能隐式包含,因为它是一个动态过程,我们希望自己进行管理。
布置新的运算符何时会显示分段错误?
放置新运算符时应格外小心。传递的地址可以是指向有效存储位置的引用或指针。当传递的地址为时,它可能会显示错误:
- 诸如NULL指针之类的指针。
- 没有指向任何位置的指针。
- 除非它指向某个位置,否则不能为空指针。
// C++ program to illustrate segmentation fault
// while using placement new operator
#include
using namespace std;
int main()
{
// Fine
int i = 10;
int *ipt = &i ;
int *i1 = new(ipt) int(9) ;
// Incorrect as ip may not
// be a valid address
int *ip;
int *i2 = new(ip) int(4) ;
// Fine
void *vd = &i;
int *i3 = new(vd) int(34) ;
// Incorrect as x is not an address
int x;
int *i5 = new(x) int(3) ;
return 0;
}
Segmentation fault
在新的运算符布局新的运算符的优势
- 内存分配的地址是事先已知的。
- 在构建内存池,垃圾收集器或仅在性能和异常安全至关重要时非常有用。
- 因为已经分配了内存,所以没有分配失败的危险,并且在预分配的缓冲区上构造对象所花费的时间更少。
- 在资源有限的环境中工作时,此功能很有用。