C++语言中很多问题都是非常细小的,例如new操作符(new operator)和new 操作(operator new)的区别:
注意:
我们生成的placement delete版本什么时候使用呢?注意一下哈!只要在placement new抛出异常时候,编译器才会调用placement delete版本,这个时候因为构造失败,所以需要释放内存。其他情况下如果我们想要使用placement delete版本时,需要调用 类名::operator delete(parameters)显示调用释放内存空间的过程。
1、new和内存分配:
new操作符:
void* operator new(size_t size);
new操作
string* ps=new string("Memory Management";
注意new操作中实际有两个步骤:
1)申请内存空间:
void *memory=operator new(sizeof(string));
2)调用构造函数进行初始化操作:
call string::string("Memory Management") on *memory;
string* ps=static_cast<string*>(memory);
如果我们针对operator new函数中不仅包括size_t参数外,还包括其他参数的话,这被称为placement new。
class Widget{
public:
Widget(int widgetSize);
...
};
Widget* constructWidgetInBuffer(void* buffer,int widgetSize){
return new(buffer) Widget(widgetSize);
}
这个函数返回一个指针,指向一个Widget对象,对象在传递给函数的buffer中进行分配。被调用的operator new函数除了带有强制参数size_t外,还必须接受void*指针参数,指向对象占用的内存空间,这种类型的operator new便是最常见的placement new,这种类型的placement new通常包括在#include <new>头文件中,其operator new实现形式可能如下:
void* operator new(size_t,void* location){
return location;
}
你必须使用语句#include <new>。
分析:
- 如果你仅仅想要在堆上建立一个对象,应该使用new操作符,可以即分配内存空间同时又可以利用构造函数进行对象构造;
- 如果你仅仅想申请内存,那么就可以调用operator new函数,仅仅只会申请内存空间而已;
- 如果你想定制自己在堆对象被建立时候的内存分配过程,你应该写下自己的operator new函数,即重载operator new函数,new操作会调用定制的operator new;
- 如果想要在一块已经获得指针的内存里面创建一个对象,应该使用placement new。
malloc的过程类似于默认的operator new函数,即简单申请内存空间;
2、delete和内存空间的释放
string* ps;
delete ps;
void operator delete(void* memoryToBeDeallocated);
所以,delete ps的过程类似于:
1)析构函数的执行:
ps->~string();
2)内存空间的释放:
这里有一个隐含的意思是如果你真想处理未被初始化的内存,你应该绕过new和delete操作符,而调用operator new获得内存和operator delete释放内存给系统。如果在创建对象的时候申请内存阶段使用的是placement new函数,那么请写好其相应的operator delete函数,在删除对内存中的对象的时候也可以删除堆内存空间。
3、operator new[]和operator delete[]
string* ps=new string[];
被使用的new仍然是new操作符,但是建立数组时new操作符的行为与单个对象建立有少许不同。
1)内存不再用operator new分配,代之用operator[]分配,它和operator new一样可以被重载,这就语序你控制数组的内存分配,就像你能控制单个数组内存分配一样;
2)使用new操作符调用析构函数的数量不同,对于数组,在数组中的每一个对象的析构函数都必须被调用。
string* ps=new string[];
正如我们可以重载operator delete函数一样,我们也可以重载operator[],在它们重载方法上面有一些限制。
new和delete操作符号是内置的,行为不受控制,凡是它们调用的内存分配和释放函数则可以控制。所以我们只能改变它们为完成它们的功能所采取的方法,而它们所完成的功能则被语言固定下来,不能改变。
示例:(下面我们通过一个例子,来看placement new和placement delete的使用)
#include <iostream>
#include <string>
#include <new>
using namespace std;
class MyString{
private:
string i;
public:
MyString(){
}
MyString(string s) :i(s){
}
void* operator new(size_t n, ostream& os){
os << "创建开始" << endl;
return ::operator new(n);
}
void operator delete(void* pMemory, ostream& os){
os << "删除开始" << endl;
return ::operator delete(pMemory);
}
~MyString(){
cout << "MyString析构开始。。。" << endl;
}
};
int main(){
MyString* ms = new (std::cout)MyString("hello");
ms->~MyString();
MyString::operator delete(ms, cout);
return ;
}
运行结果:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX90zdNNTSE9keBRkT4FEVkZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DN3cDOwYTM2ETMzgDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
这儿我们发现,如果此时调用失败,我们可以显示调用其内存空间释放的过程,即使用编译器提供的默认delete函数,当然我们也可以自己显示调用析构函数,然后再显示调用placement delete函数。
但是请读者考虑如下问题,placement new中有一个极其特殊的版本,包含在#include <new>头文件中,这样可能导致一个堆内存空间中包含另一个对象在堆中的内存空间,即两个对象的堆内存空间重合,说的更仔细一些就是包含,那么此时我们应该怎样进行析构呢???
#include <iostream>
#include <string>
#include <new>
using namespace std;
class MyString{
private:
string i;
public:
MyString(){
}
MyString(string s) :i(s){
}
~MyString(){
cout << "MyString析构开始。。。" << endl;
}
};
class MyInt{
private:
int i;
public:
MyInt(){
}
MyInt(int i) :i(i){
}
void operator delete(void *pMemory, void *i){
cout << "调用了" << endl;
::delete pMemory;
}
void show(){
cout << i << endl;
}
~MyInt(){
cout << "MyInt析构开始。。。" << endl;
}
};
int main(){
MyString* ms = new MyString();
cout << ms <<" "<< ms + sizeof(*ms) << endl;
MyInt* mi = new (ms)MyInt();
mi->show();
cout << mi << " " << mi + sizeof(*mi) << endl;
mi->~MyInt();
MyInt::operator delete(mi, ms);
return ;
}
运行结果:
经过不懈的努力,我们最后确定了针对#include <new>这种类型的placement new的删除操作,避免内存泄露问题的发生,注意在堆中内存的释放顺序!
1)堆中的效果:
#include <iostream>
#include <string>
#include <new>
using namespace std;
class MyString{
private:
string i;
public:
MyString(){
}
MyString(string s) :i(s){
}
~MyString(){
cout << "MyString析构开始。。。" << endl;
}
};
class MyInt{
private:
int i;
public:
MyInt(){
}
MyInt(int i) :i(i){
}
void show(){
cout << i << endl;
}
~MyInt(){
cout << "MyInt析构开始。。。" << endl;
}
};
int main(){
MyString* ms = new MyString();
cout << &ms << endl;
cout << ms <<" "<< ms + sizeof(*ms) << endl;
MyInt* mi = new (ms)MyInt();
cout << mi << " " << mi + sizeof(*mi) << endl;
mi->show();
delete mi;
delete ms;
return ;
}
运行结果:
2)栈中的效果:
#include <iostream>
#include <string>
#include <new>
using namespace std;
class MyString{
private:
string i;
public:
MyString(){
}
MyString(string s) :i(s){
}
~MyString(){
cout << "MyString析构开始。。。" << endl;
}
};
class MyInt{
private:
int i;
public:
MyInt(){
}
MyInt(int i) :i(i){
}
void show(){
cout << i << endl;
}
~MyInt(){
cout << "MyInt析构开始。。。" << endl;
}
};
int main(){
int a[]={,,,,,,,,,};
int*ms=a;
MyInt* mi = new (&ms)MyInt();
cout << mi << " " << mi + sizeof(*mi) << endl;
mi->show();
delete mi;
//delete ms;
return ;
}
运行结果: