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

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(),不管内存分配,只管调用构造函数构建对象,此时需要手动管理内存,并单独析构;

猜你喜欢

转载自www.cnblogs.com/chenny7/p/12103666.html
今日推荐