重载new的分析III

来源:互联网 发布:张剑150篇怎么样 知乎 编辑:程序博客网 时间:2024/05/21 06:38

作者:baihacker
来源:http://hi.baidu.com/feixue
=============本站原创,转载请注明出处=============
继续使用重载的说法似乎不恰当了,因为有的是replace而不是overload,这
里还是不改变标题吧。
在文1中主要是通过一些实验,进行了一些推测。
在文2中引用了标准,解读了关键部分,同时写了两个示例程序和一个有bug
的内存泄漏检查程序。
而本文主要是对文2进行进一步补充,还有就是写了一些更实用的东西。
文1:http://hi.baidu.com/feixue/blog/item/5c310cd781b4bcdfa144dfc6.html
文2:http://hi.baidu.com/feixue/blog/item/943d59825ca582a80cf4d242.html

1.
文2中提到一些operator new或delete是replaceable,但是似乎没有说
是哪些,这里说一下:前8个。

2.
关于placement的理解:
a.new后面加个括号跟一堆参数,可以称之为placement语法。
b.在存储分配回收函数中,new,delete可以分为三类:
单个对象形式,数组形式,placement形式。
前面两个就是指new,delete的单个对象形式和数组形式,包含了有异常的
和无异常的。然而,无异常的通过placement语法传入一个nothrow_t对象。
而placement形式是特指带了一个void*参数的形式。
c.在类中声明的delete可以只有一个void*参数,或者一个void*参数和size_t
参数(相应的数组形式同理)。而这些delete称为usual (non-placement)
deallocation function。
可见placement在不同的语境中有三层含义。

3.
文2中提到
//delete new(area) X;//虽然可以运行,但是不好,用了

placement deallocation function和placement allocation function对应。
同样的,对应的non-placement的也要对应。
(注意到前文只是在类中的delete提到usual (non-placement) deallocation
function,所以这里假定这条对应关系是在一个类中起作用)

这条注释掉的部分,其输出结果是:
X::operator new:        12:0x472020
X::operator delete(size):       0x472020:12

首先看看为什么有这样的输出:
因为是在delete-expression所以会输出在类中的delete版本。
与之形成对比的是operator delete(new(area) X, area);
会忘记了new-expression的类型,转为void*,进而不以调用X中的delete版本。
其次,看看这句的ill-formed:
delete new(area) X;分配是placement的,回收不是placement的,所以
不好。
另外,再看一个ill-formed的例子
在n3225中的例子是:
struct S {
static void* operator new(std::size_t, std::size_t);
static void operator delete(void*, std::size_t);
};
S* p = new (0) S;
因为没有合适的placement的回收,所以不好。

另外,在文2中:delete new X;的输出是
X::operator new:        12
X::operator delete:     0x46f020
在这里用的新版本的编译器,其结果是:
X::operator new:        12
X::operator delete(size):       0x472020:12
可见其不同,新版本的编译器更合适一些。

由于标准的non-placement版本有两个,所以在为类写delete的时候造成要注意
选择合适的版本。

4.标准库中的12个函数的用处。
前文的理论性强了,而且在实际中似乎看不到用处,但是这一节的用处比较大。
a.单个对象的分配和回收:
new的普通版本和nothrow版本,调用的语法都很清楚,不再多说。
delete的普通版本,也不用多说。
上面的都是通过new-expression或delete-expression调用。
而delete的nothrow版本是nothrow版本的new在构造函数异常的时候调用。
进一步思考可以知道,nothrow版本的new出来的指针,在delete ptr;的时候不
会调用nothrow版本的delete。

如果需要replace这些东东,千万小心。

b.数组版本的和单个对象的版本相对应。

c.placement版本的。
首先,这一系列函数共四个,因为没有需要抛出异常的版本的,另外,这些是
不能replace的。
标准中说了一句:Intentionally performs no other action.
同时,这些delete在placement new调用构造函数的时候异常的时候调用。

5.关于new-expression和functional new-calling。
functional new-calling是我生造出来的。
new-expression是一般的new语法,含placement。
而functional new-calling是指 operator new(参数列表)的调用。
同样,对应的还有delete-expression和functional delete-calling。

expression版本的new可以调用构造函数。
functional版本的视其具体情况而定(可以是函数模版)。

同样对应的delete一个会调用析构函数,另一个也视其情况而定。

所以在文2中给的那个内存泄漏检查是有大bug的。

对于new通过宏替换为自定义的placement语法的,这个毫无问题。
效果是调用自己定义的new然后调用构造函数。
而对于delete只调用自己定义的delete不会调用相应的析构函数。
所以应该在前面加一句delete ptr。而这个带来的行为就是析构函数的调用。
同时还有::operator delete(void* ptr)的调用。然后再调用自己定义的
delete进行标记。
这样:
void* operator new(size_t size, 其它参数)
{
  void* ret = ::operator new (size);
  记录ret;
  return ret;
}
void operator delete(void* ptr, 其它参数)
{
  记录ptr;
  //::operator delete (p); 这个注释掉
}
#define   dbg_new    new(TO_TSTRING(__FILE__), __LINE__, TO_TSTRING(__FUNCTION__))
#define   dbg_delete(x) (operator delete (x, TO_TSTRING(__FILE__), __LINE__, TO_TSTRING(__FUNCTION__)), delete x)
#define   dbg_deletea(x) (operator delete [] (x, TO_TSTRING(__FILE__), __LINE__, TO_TSTRING(__FUNCTION__)), delete[] x)
(宏中可能还有些地方需要加上(),这里暂时不考虑了)
另外,当x有副作用的时候,宏不能正常工作了。

如果需要写一个自己分配器的话,那么最好是把那8个函数全部replace了。
如果只记录分配回收,那么在分配器里面可以实现。
如果还要记录分配的其它信息,比如在哪一行调用的,则最好在外面加一层:
replace那8个函数,另外再重载一些new。
再仔细想想,在new中实现分配和记录其它信息是完全没有问题的,而delete
是不行的。
但是如果这样的话,对于delete x;在调用析构函数后会调用:operator delete(x)。
所以,至少把delete replace了是必须的。但是分配是在重载的new中,回收是在
replace的delete中,这样显得很不优美,所以最好是连new也replace了。除此之
外,不replace new的话库中的new的行为可能会带来一些意想不到的结果。

从这里可以看出来new和delete的不对称性,其原因在于delete少了一个放置语法。
如果delete也有放置语法,则可以完全自己重载new和delete,而不用去replace了。

从这些东西也可以进一步看出来new-expression和functional new-calling的不同。
placement语法的存在,使得在new-expression中调用不同的new的同时也能正确调
用构造函数。而delete的没有placement语法使得负担很重:
宏中x的副作用;
对delete的replace的必要性;
replace带来的影响。
否则,直接一个delete的placement语法就解决问题,而不影响原有的new和delete。

2011/02/17 13:43
第5节中dbg_delete和dbg_deletea(x)中delete放到,后面去了。

2013/06/29

这系列三篇文章写得比较烂,为了完整性先丢在这里,以后再好好搞搞。

原创粉丝点击