关于C++中new和delete的特殊用法
acmilan2015/05/18软件综合 IP:四川
一、在特定内存区上构造对象
有时候我们希望在某些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;
}</iostream>
上边出现了显式调用析构函数的语句,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;
}</iostream>
如果在类中重载了一对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[]运算符,不建议全局重载,以免产生麻烦。

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

[修改于 9年8个月前 - 2015/05/25 01:16:34]

来自:计算机科学 / 软件综合
11
 
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
张静茹
9年8个月前 IP:山东
769226
看不懂..............
-1
学术分
1211
2015-05-18
违反增量规则的规定,情节较为轻微,减轻处罚1学术分。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
drzzm32
9年8个月前 IP:陕西
770077
居然能用new。。。似乎大部分教材并未说到C++有new关键字,倒是我有次把C++当成C#,做类初始化,new蓝了。。。

在特定内存上构造看起来是占用了char型变量的内存空间?
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
9年8个月前 IP:四川
770146
引用 drzzm32:
居然能用new。。。似乎大部分教材并未说到C++有new关键字,倒是我有次把C++当成C#,做类初始化,new蓝了。。。

在特定内存上构造看起来是占用了char型变量的内存空间?
小教程可能不会有,但是c++ primer这种合格的教材是一定会有的。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
金星凌日
9年8个月前 IP:陕西
770160
引用 drzzm32:
居然能用new。。。似乎大部分教材并未说到C++有new关键字,倒是我有次把C++当成C#,做类初始化,new蓝了。。。

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

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

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

所属专业
上级专业
同级专业
acmilan
进士 学者 笔友
文章
461
回复
2934
学术分
4
2009/05/30注册,5年11个月前活动
暂无简介
主体类型:个人
所属领域:无
认证方式:邮箱
IP归属地:未同步
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}