📜  在C++中放置新运算符

📅  最后修改于: 2021-05-30 20:09:56             🧑  作者: Mango

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的新值。通过&Xmem的值相等这一事实可以清楚地看出这一点。
下图以图形方式显示了以上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

在新的运算符布局新的运算符的优势

  • 内存分配的地址是事先已知的
  • 在构建内存池,垃圾收集器或仅在性能和异常安全至关重要时非常有用。
  • 因为已经分配了内存,所以没有分配失败的危险,并且在预分配的缓冲区上构造对象所花费的时间更少。
  • 在资源有限的环境中工作时,此功能很有用。
要从最佳影片策划和实践问题去学习,检查了C++基础课程为基础,以先进的C++和C++ STL课程基础加上STL。要完成从学习语言到DS Algo等的更多准备工作,请参阅“完整面试准备课程”