天天看点

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

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>。

分析:

  1. 如果你仅仅想要在堆上建立一个对象,应该使用new操作符,可以即分配内存空间同时又可以利用构造函数进行对象构造;
  2. 如果你仅仅想申请内存,那么就可以调用operator new函数,仅仅只会申请内存空间而已;
  3. 如果你想定制自己在堆对象被建立时候的内存分配过程,你应该写下自己的operator new函数,即重载operator new函数,new操作会调用定制的operator new;
  4. 如果想要在一块已经获得指针的内存里面创建一个对象,应该使用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 ;
}
           

运行结果:

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

这儿我们发现,如果此时调用失败,我们可以显示调用其内存空间释放的过程,即使用编译器提供的默认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 ;
}
           

运行结果:

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

经过不懈的努力,我们最后确定了针对#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 ;
}
           

运行结果:

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

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 ;
}
           

运行结果:

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

继续阅读