C++ new与delete

C++ new与delete

 

new operator 和 delete operator

new operator 和delete operator 是运算符

我们知道new运算符会干2件事:申请内存和调用对象构造函数,比如,当我们new一个string对象:

string *ps = new string("Hands up!")

编译器实际的工作大概是这样:

void *mem = operator new(sizeof(string));      //申请内存
call string::string("Hands up!") on *mem;     // 调用构造函数
string *ps = static_cast<string*>(mem);       // 返回string*指针

 

同样,delete运算符也会干2件事:调用对象析构函数和释放内存

delete ps

编译器实际的工作大概是这样:

ps->~string()
operator delete(ps)

 

编译器看到类类型的new或者delete运算符的时候,首先查看该类是否是有operator new或者operator delete成员,如果类定义了自己的operator new和operator delete函数,则使用这些函数为对象分配和释放内存,否则调用标准库版本(:: operator new和:: operator delete)。

 一个例子:

struct foo {
    foo(int x) {m = x; printf("foo(%d)\n", m);}
    ~foo() {printf("~foo()\n");}
    int get() {printf("m=%d\n", m);}

    static void* operator new(size_t size) {
        printf("operator new()\n");
        return ::operator new(size);
    }   
    static void operator delete(void* ptr) {
        printf("operator delete()\n");
        ::operator delete(ptr);
    }   

    private:
        int m;
};

int main() {
    foo* f = new foo(10);
    f->get();
    delete f;

    return 0;
}

输出结果

operator new()
foo(10)
m=10
~foo()
operator delete()

可见,当调用new operator的时候,会先调用operator new()分配内存,然后调用构造函数;当调用delete operator的时候,先调用析构函数,然后调用operator delete()释放内存。

 


operator new() 和 operator delete()

operator new()和operator delete() 是函数,跟operator =() 类似。

表达式:

void *operator new(size_t);
void *operator new[](size_t);
void  operator delete(void*);
void  operator delete[](void*);

前面提到,全局的::operator new() 内部其实就是调用malloc分配内存,而::operator delete() 内部就是调用free释放内存。

operator new() 和operator delete() 都可以重载。

 重载operator new时:

  • 第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t,此外,还可以带其它参数;
  • 只分配需要的空间,但不调用构造函数;当无法满足所需内存空间时,如果有new_handler,则调用new_handler;

 

看一个带额外参数的operator new()的例子

struct foo {
    foo(int x) {m = x; printf("foo(%d)\n", m);}
    ~foo() {printf("~foo()\n");}
    int get() {
        printf("m=%d\n", m); 
    }   

    static void* operator new(size_t size, char* desc) {
        printf("operator new(%s)\n", desc);
        void* p = ::operator new(size);
        return p;
    }   
    static void operator delete(void* ptr) {
        printf("operator delete()\n");
        ::operator delete(ptr);
    }   
    static void operator delete(void* ptr, char* desc) {
        printf("operator delete(%s)\n", desc);
        ::operator delete(ptr);
    }   
    private:
        int m;
};

int main() {
    //foo* e = new foo(10); // Error,
    foo* f = new("hello") foo(10);
    f->get();
    delete f;

    return 0;
}

输出结果:

operator new(hello)
foo(10)
m=10
~foo()
operator delete()

代码中,

foo* e = new foo(10) 会发生错误的原因是 member function(成员函数)的名字会覆盖外围的具有相同名字的函数。
delete f 调用的是operator delete()常规版本。

考虑一下这种情况,operator new()内存分配成功,但构造函数里面抛出异常,runtime system(运行时系统)有责任撤销 operator new() 所执行的分配。然而,runtime system并不能了解被调用的 operator new() 版本是如何工作的,所以它自己无法撤销那个分配。runtime system转而寻找一个和 operator new 持有相同数量和类型额外参数的 operator delete 版本,例如:

struct foo {
    foo(int x) {
        m = x; printf("foo(%d)\n", m);
        throw this;
    }
    ~foo() {printf("~foo()\n");}
    int get() { printf("m=%d\n", m); }

    static void* operator new(size_t size, char* desc) {
        printf("operator new(%s)\n", desc);
        void* p = ::operator new(size);
        return p;
    }   
    static void operator delete(void* ptr) {
        printf("operator delete()\n");
        ::operator delete(ptr);
    }   
    static void operator delete(void* ptr, char* desc) {
        printf("operator delete(%s)\n", desc);
        ::operator delete(ptr);
    }   
    private:
        int m;
};

int main() {
    try {
        foo* f = new("hello") foo(10);
        f->get();
        delete f;
    } catch(void* e) {
        printf("catch exception");
    }

    return 0;
}

输出:

operator new(hello)
foo(10)
operator delete(hello)

这时,带额外参数的operator delete被调用了,如果没有这个额外参数版本,就发生了内存泄露。

如上,规则很简单:对一个带有额外参数的 operator new(),必须既提供常规 operator delete(用于构造过程中没有抛出 exception(异常)时),又要提供一个持有与 operator new 相同的 额外参数的版本(用于构造有异常的情况)。

 

再看下operator new[]和operator delete[]的例子:

struct foo {
    foo() {printf("foo()\n");}
    foo(int x) { m = x; printf("foo(%d)\n", m); }
    ~foo() {printf("~foo()\n");}
    int get() { printf("m=%d\n", m); }

    static void* operator new(size_t size) {
        printf("operator new(%d)\n", size);
        void* p = ::operator new(size);
        return p;
    }
    static void* operator new[](size_t size) {
        printf("operator new[](%d)\n", size);
        void* p = ::operator new[](size);
        return p;
    }   
    static void operator delete(void* ptr, size_t size) {
        printf("operator delete(%d)\n", size);
        ::operator delete(ptr);
    }
    static void operator delete[](void* ptr, size_t size) {
        printf("operator delete[](%d)\n", size);
        ::operator delete[](ptr);
    }   
    private:
        int m;
};

int main() {
    const int len = 3;
    foo* f = new foo[len]();
    for (int n=0; n<len; n++) {
        f[n].get();
    }
    delete []f;

    return 0;
}

 

 

new 失败的处理

operator new 在空间不足导致内存分配失败时:

  1. 调用全局的new_handler 类型的函数,这个函数的默认处理是抛出std::bad_alloc异常;
  2. 可以通过 set_new_handler(void(*new_handler)) 自定义new_handler函数,具体实现一般是内存碎片整理;
  3. 当operator new不能满足一个内存请求的时候,它会反复调用new_handler函数直到它发现有足够的内存可以分配

 

设置全局的new_handler

  void my_new_handler() {
    cout << "No enough memory" << endl;
    exit(1);
}

int main() {
    set_new_handler(my_new_handler);
    int *pBigDataArray = new int[100000000L];

    delete [] pBigDataArray;
}

如果operaotr new无法为100,000,000个整数分配内存,就会调用outOfMem,也就是输出一个error信息之后程序终止(exit调用);

 

与operator new的重载类似,我们也可以为某个类自定义new_handler, 从而代替全局的new_handler,例如

void my_new_handler() {
    cout << "No enough memory" << endl;
    exit(1);
}

class NewHandlerHolder {
public:
    explicit NewHandlerHolder(std::new_handler nh): handler(nh) {} // new-handler
    ~NewHandlerHolder() { std::set_new_handler(handler); }

private:
    std::new_handler handler;
    NewHandlerHolder(const NewHandlerHolder &) =delete;
    NewHandlerHolder& operator=(const NewHandlerHolder &) =delete;
};

class Widget {
public:
    int data;

    static std::new_handler set_new_handler(std::new_handler p) throw();
    static void *operator new(std::size_t size) throw(std::bad_alloc);

private:
    static std::new_handler currentHandler;
};

std::new_handler Widget::currentHandler = NULL;

std::new_handler Widget::set_new_handler(std::new_handler p) throw() {
    std::new_handler oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;
}

void *Widget::operator new(std::size_t size) throw(std::bad_alloc) {
    NewHandlerHolder h(std::set_new_handler(currentHandler)); // install Widget’s
    return ::operator new(size);        // allocate memory
    // or throw
}

int main() {
    Widget::set_new_handler(my_new_handler);

    Widget* w1 = new Widget;    // if memory allocation fails, call my_new_handler

    string *ps = new string;    // if memory allocation fails, call the global new_handler

    Widget::set_new_handler(0);
    Widget *pw2 = new Widget;   // if memory allocation fails, throw an exception immediately. (There is no new_handler for Widget)
}

这里,Widget类的operator new将会做下面的事情:

  1. 调用标准set_new_handler,参数为Widget的错误处理函数,这就将Widget的new-handler安装成为了全局的new-handler。
  2. 调用全局的operator new来执行实际的内存分配。如果分配失败,全局的operator new会触发Widget的new-handler,因为这个函数已经被安装为全局new-handler。如果全局的operator new最终不能分配内存,它会抛出bad_alloc异常。在这种情况下,Widget的operator new必须恢复原来的全局new-handler,然后传播异常。为了确保源new-handler总是能被恢复,Widget将全局new-handler作为资源来处理,使用资源管理对象来防止资源泄漏。
  3. 如果全局operator new能够为Widget对象分配足够的内存。Widget的operator new就会返回指向被分配内存的指针。管理全局new-handler的对象的析构函数会自动恢复调用Widget的operator new之前的new-handler。

 

 

 

placement new

placement new()是operator new()的一个重载版本,如果想在已经分配的内存中创建一个对象,使用new是不行的。

placement new允许在一个已经分配好的内存中(栈或堆中)构造一个新的对象。

表达式:

new(place_address) T
new(place_address) T(initializer_list)

其中,

  • place_address 参数必须是一个指针,指向一块预分配好的内存;
  • initializer_list 提供了初始化列表(可能是空的),以便在构造新分配的对象中使用。

 

看个例子:

struct foo {
    foo(int x) {m = x; printf("foo(%d)\n", m);}
    ~foo() {printf("~foo()\n");}

    private:
        int m;
};

int main() {
    void* addr = malloc(sizeof(foo));

    foo* p = new(addr) foo(10);
    p->~foo();

    free(p);

    return 0;
}

可见,placement new不能使用delete(因为没有申请内存,placement new()没有与之对应的 placement delete())。因此,析构函数需要手动调用。

 

也可以使用栈内存

struct foo {
    foo(int x) {m = x; printf("foo(%d)\n", m);}
    ~foo() {printf("~foo()\n");}
    int get() {printf("m=%d\n", m);}

    private:
        int m;
};

int main() {
    char mem[1024];

    foo* p = new(mem) foo(10);
    p->get();
    p->~foo();

    return 0;
}

 

 

 

总结下区别:

  • new operator 既分配内存,又构建对象(通过构造函数),与之相反的是delete operator;
  • operator new(),只管分配内存,不构建对象(不会调用构造函数),与之相反的是operator delete();
  • placement new(),不管内存分配,只管调用构造函数构建对象,此时需要手动管理内存,并单独析构;

 

posted @ 2019-12-26 21:40  如果的事  阅读(1174)  评论(0编辑  收藏  举报