关于C++中new和delete的特殊用法
一、在特定内存区上构造对象
有时候我们希望在某些C++触及不到的地方放置C++对象,就需要用到这个方法。方法是使用new的就地初始化形式,即new(void*)调用构造函数构建对象,并在必要的时机手动调用析构函数进行销毁。
#include <iostream>
using namespace std;
                                   
struct MyClass {
    MyClass() {
        cout << "构造函数" << endl;
    }
    virtual ~MyClass() {
        cout << "析构函数" << endl;
    }
};
                                    
int main()
{
    char k[sizeof(MyClass)]; // 预先分配内存k[]
    MyClass* ptr1 = new(k) MyClass; // 在k[]中生成新对象 new(void*) MyClass();
    ptr1->~MyClass(); // 直接调用析构函数,销毁对象
                                    
    char m[sizeof(int) + 2*sizeof(MyClass)]; // 预先分配内存m[](计数空间+数据空间)
    MyClass* ptr2 = new(m) MyClass[2]; // 在m[]中生成新MyClass对象
    ptr2[1].~MyClass(); // 直接调用析构函数,销毁对象
    ptr2[0].~MyClass(); // 直接调用析构函数,销毁对象
                                    
    return 0;
}
上边出现了显式调用析构函数的语句,C++中析构函数是可以直接调用的。而构造函数却不能直接调用(直接调用的含义是新建临时值对象),需要使用new(void*)这种特殊方法。

二、重载new和delete运算符
大多数运算符只有一种已知形式(有的如++或--有两种),但是new、new[]比较特别,标准形式的有三种。以单变量new和delete为例,适用于数组的new[]和delete[]与之类似:
1. 常规new运算符(new)分配内存,并调用构造函数;可能会返回有效地址,也可能抛出bad_alloc异常。
2. 无抛出new运算符(new(nothrow))分配内存,并调用构造函数;可能会返回有效地址,也可能会返回NULL。
3. 就地new运算符(new(void*))不分配内存,只调用构造函数;直接返回有效地址。
4. delete运算符(delete)调用析构函数,并释放内存。
对于new[],特别的,在内存中分配时还需要一个int计数字段。所以,对于new[],需要的内存总数是sizeof(int) + n * sizeof(MyClass)
三个new和一个delete的显式调用方法如下:
// 单变量形式
ptr1 = new MyClass(...);
ptr2 = new(nothrow) MyClass(...);
ptr3 = new(&buffer) MyClass(...); // 此处需要 sizeof(MyClass) 字节空间
delete ptr1; // 示例:析构并释放ptr1指向的对象
                             
// 数组形式
ptr1 = new MyClass[3](...);
ptr2 = new(nothrow) MyClass[3](...);
ptr3 = new(&buffer) MyClass[3](...); // 此处需要 sizeof(int) + 3 * sizeof(MyClass) 字节空间
delete[] ptr1; // 示例:析构并释放ptr1指向的对象数组
new和delete运算符的重载(operator new、operator delete)并不需要调用构造函数或析构函数,operator new只需分配内存,并返回内存块的起始地址,而operator delete只需释放内存。
operator new和operator delete也分三种,而且每一种要成对重载。其中operator delete只有第一种能显式调用,其它两种都不能,它们的用途是在对应的new运算符执行期间,分配内存成功但构造函数抛出异常时,被隐式调用:
// 单变量形式
void* operator new(size_t sz);                 // 常规new(可抛出bad_alloc异常)
void* operator new(size_t sz, nothrow_t nt);   // 无抛出new(可返回NULL)
void* operator new(size_t sz, void *place);    // 就地new(直接返回place,不分配内存)
void operator delete(void *obj);               // 显式delete & “常规new”的隐式delete
void operator delete(void *obj, nothrow_t nt); // “无抛出new”的隐式delete
void operator delete(void *obj, void *place);  // “就地new”的隐式delete
                          
// 数组形式
void* operator new[](size_t sz);                 // 常规new[](可抛出bad_alloc异常)
void* operator new[](size_t sz, nothrow_t nt);   // 无抛出new[](可返回NULL)
void* operator new[](size_t sz, void *place);    // 就地new[](直接返回place,不分配内存)
void operator delete[](void *obj);               // 显式delete[] & “常规new[]”的隐式delete[]
void operator delete[](void *obj, nothrow_t nt); // “无抛出new[]”的隐式delete[]
void operator delete[](void *obj, void *place);  // “就地new[]”的隐式delete[]
比如以下代码可以让C++调用对应的隐式delete——
#include <iostream>
using namespace std;
                                   
struct MyClass{
    MyClass() { // 构造函数
        cout << "抛出异常的构造函数" << endl;
        throw exception(""); // 抛出异常
    }
};
                                                       
int main() {
    try {
        MyClass *ptr = new(nothrow) MyClass; // 分配成功,但构造函数抛出异常
        delete ptr; // 这句不会被执行
    } catch (exception& e) {
        // 自动执行void operator delete(void *, nothrow_t)
    }
    return 0;
}
如果在类中重载了一对new和delete,那么在new或delete这个类的对象时,C++只会在类中搜索new或delete,不会在全局搜索。所以,对于重载new或delete运算符的C++类,需要同时重载三种形式。
如果不想自己写内存分配,只想给这些运算符添加功能,可以直接调用顶级命名空间的::operator new和::operator delete等来完成运算符的功能(这些预定义重载总是存在且可以被显式调用的),例如
struct MyClass {
    void* operator new(size_t sz) {
        cout << "operator new" << endl;
        return ::operator new(sz); // 调用全局new
    }
    void operator delete(void * ptr) {
        cout << "operator delete" << endl;
        return ::operator delete(ptr); // 调用全局delete
    }
};
实际上operator new和operator new[]的参数数量并没有限制,形式可以为void* operator new(size_t, ...),只需要保证第一个参数size_t和返回值void*的类型正确即可。这些重载形式均可以用new(...)形式调用。但是也要注意,如果写了某一个new重载,那么还需要再写一个相对应形式的void operator delete(void*, ...)以处理构造函数抛出异常的情况,同样需要注意第一个参数void*和返回值void的类型正确。
struct MyClass {
    void* operator new(size_t sz, string msg, int val) // 自定义new运算符
    {
        cout << "new: " << msg.c_str() << " " << val << endl;
        return ::operator new(sz);
    }
    void operator delete(void *ptr, string msg, int val) // 对应的隐式delete
    {
        cout << "delete: " << msg.c_str() << " " << val << endl;
        return ::operator delete(ptr);
    }
};
                              
MyClass *ptr1 = new("creating object...", 2) MyClass; // 调用方法
::delete ptr1; // 没有重载显式delete,所以要调用::delete
程序可以调用::new和::delete访问全局的new或delete,但是除非有必要,不建议这样做。
最好只在自己的类中重载默认三种形式的new、new[]、delete、delete[]运算符,不建议全局重载,以免产生麻烦。

如上所述。如有疏漏请指正。

[修改于 4 年前 - 2015-05-25 01:16:34]

来自 软件综合
 
2015-5-18 07:01:02
1楼
看不懂..............
-1  学术分    1211   2015-05-18   违反增量规则的规定,情节较为轻微,减轻处罚1学术分。
折叠评论
加载评论中,请稍候...
折叠评论
2015-5-23 09:20:55
2015-5-23 09:20:55
2楼
居然能用new。。。似乎大部分教材并未说到C++有new关键字,倒是我有次把C++当成C#,做类初始化,new蓝了。。。

在特定内存上构造看起来是占用了char型变量的内存空间?
折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
3楼
引用 drzzm32:
居然能用new。。。似乎大部分教材并未说到C++有new关键字,倒是我有次把C++当成C#,做类初始化,new蓝了。。。

在特定内存上构造看起来是占用了char型变量的内存空间?
小教程可能不会有,但是c++ primer这种合格的教材是一定会有的。
折叠评论
加载评论中,请稍候...
折叠评论
4楼
引用 drzzm32:
居然能用new。。。似乎大部分教材并未说到C++有new关键字,倒是我有次把C++当成C#,做类初始化,new蓝了。。。

在特定内存上构造看起来是占用了char型变量的内存空间?
我觉得那个在局部char[]里placement new的用法只是为了演示。
placement new我只用过一次,是调用Lua的API。Lua的C API中有一个函数返回一个由Lua虚拟机管理的内存的指针,如果要给里面放C++的类实例,就必须用placement new。
折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
5楼
引用 金星凌日:
我觉得那个在局部char[]里placement new的用法只是为了演示。
placement new我只用过一次,是调用Lua的API。Lua的C API中有一个函数返回一个由Lua虚拟机管理的内存的指针,如果要给里面放C++的类实例,...
placement new也就是这个用途。一般来说用不着placement new
折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
6楼
引用 drzzm32:
居然能用new。。。似乎大部分教材并未说到C++有new关键字,倒是我有次把C++当成C#,做类初始化,new蓝了。。。

在特定内存上构造看起来是占用了char型变量的内存空间?
和c#中的new不一样的是,c++中的new返回的是指针,而且必须手动delete。
折叠评论
加载评论中,请稍候...
折叠评论
7楼
你最后那段代码g++编译通不过,提示“no suitable ‘operator delete’ for ‘MyClass’”。
此外,我记得C++标准是禁止重载placement new操作符的。
折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
8楼
引用 金星凌日:
你最后那段代码g++编译通不过,提示“no suitable ‘operator delete’ for ‘MyClass’”。
此外,我记得C++标准是禁止重载placement new操作符的。
忘记了,已加上全局说明符。。。
折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
9楼
引用 金星凌日:
你最后那段代码g++编译通不过,提示“no suitable ‘operator delete’ for ‘MyClass’”。
此外,我记得C++标准是禁止重载placement new操作符的。
C++标准不允许全局重载placement new,因为这个函数被定义为inline。但允许在类中重载。
而且如果在类中只重载另外2种形式,没重载这个形式,以后调用new(place)就得用::new(place),这是非常不方便的。
只需要注意不要全局重载,只在类中重载就可以了(文中已说明)。因为标准库中可能已经定义了相同的重载函数。

[修改于 4 年前 - 2015-05-23 17:44:15]

折叠评论
加载评论中,请稍候...
折叠评论
2015-5-24 22:50:53
2015-5-24 22:50:53
10楼
引用 acmilan:
C++标准不允许全局重载placement new,因为这个函数被定义为inline。但允许在类中重载。
而且如果在类中只重载另外2种形式,没重载这个形式,以后调用new(place)就得用::new(place),这是非常不方便的。
...
多谢指教。看来是我没细看C++标准。
折叠评论
加载评论中,请稍候...
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

插入资源
全部
图片
视频
音频
附件
全部
未使用
已使用
正在上传
空空如也~
上传中..{{f.progress}}%
处理中..
上传失败,点击重试
{{f.name}}
空空如也~
(视频){{r.oname}}
{{selectedResourcesId.indexOf(r.rid) + 1}}
ID:{{user.uid}}
{{user.username}}
{{user.info.certsName}}
{{user.description}}
{{format("YYYY/MM/DD", user.toc)}}注册,{{fromNow(user.tlv)}}活动
{{submitted?"":"投诉"}}
请选择违规类型:
{{reason.description}}
支持的图片格式:jpg, jpeg, png