C++之理解不同含义的new和delete(6)---《More Effective C++》

xiaoxiao2021-02-28  95

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)内存空间的释放:

operator delete(ps);

这里有一个隐含的意思是如果你真想处理未被初始化的内存,你应该绕过new和delete操作符,而调用operator new获得内存和operator delete释放内存给系统。如果在创建对象的时候申请内存阶段使用的是placement new函数,那么请写好其相应的operator delete函数,在删除对内存中的对象的时候也可以删除堆内存空间。

3、operator new[]和operator delete[]

string* ps=new string[10];

被使用的new仍然是new操作符,但是建立数组时new操作符的行为与单个对象建立有少许不同。 1)内存不再用operator new分配,代之用operator[]分配,它和operator new一样可以被重载,这就语序你控制数组的内存分配,就像你能控制单个数组内存分配一样; 2)使用new操作符调用析构函数的数量不同,对于数组,在数组中的每一个对象的析构函数都必须被调用。

string* ps=new string[10];

正如我们可以重载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 0; }

运行结果: 这儿我们发现,如果此时调用失败,我们可以显示调用其内存空间释放的过程,即使用编译器提供的默认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(10); mi->show(); cout << mi << " " << mi + sizeof(*mi) << endl; mi->~MyInt(); MyInt::operator delete(mi, ms); return 0; }

运行结果:

经过不懈的努力,我们最后确定了针对#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(10); cout << mi << " " << mi + sizeof(*mi) << endl; mi->show(); delete mi; delete ms; return 0; }

运行结果:

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[10]={1,2,3,4,5,6,7,8,9,10}; int*ms=a; MyInt* mi = new (&ms)MyInt(10); cout << mi << " " << mi + sizeof(*mi) << endl; mi->show(); delete mi; //delete ms; return 0; }

运行结果:

转载请注明原文地址: https://www.6miu.com/read-47359.html

最新回复(0)