读书笔记 effective c++ Item 51 实现new和delete的时候要遵守约定

小编 2026-06-23 阅读:1040 评论:0
Item 50中解释了在什么情况下你可能想实现自己版本的operator new和operato...

Item 50中解释了在什么情况下你可能想实现自己版本的operator new和operator delete,但是没有解释当你实现的时候需要遵守的约定。遵守这些规则并不是很困难,但是它们其中有一些并不直观,所以知道这些规则是什么很重要。

1. 定义operator new的约定

1.1 约定列举

我们以operator new开始。实现一个一致的operator new需要有正确的返回值,在没有足够内存的时候调用new-handling函数(见Item 49),并且做好准备处理没有内存可分配的情况。你也想避免无端的隐藏“正常”版本的new,但这是一个类接口的问题而不是实现需求问题;它会在Item 52中进行处理。

Operator new的返回值部分很简单,因为operator new事实上会尝试多次分配内存,在内次分配失败之后都会调用new-handling函数。这里的假设是new-handling函数可能会做一些事情来释放一些内存。只有在指向new-handling函数的指针为null的情况下,operator new才会抛出异常。

好奇的是,C++即使在请求0个byte的时候也需要operator new返回一个合法的指针。(这个听上去很奇怪的要求简化了语言中的某些事情。)这就是基本情况,一个非成员operator new的伪代码会是像下面这个样子:

 1 void* operator new(std::size_t size) throw(std::bad_alloc) 2 { // your operator new might 3  4 using namespace std;             // take additional params 5  6 if (size == 0) {          // handle 0-byte requests 7  8  9 size = 1; // by treating them as10 } // 1-byte requests11 while (true) {12 attempt to allocate size bytes;13 14 if (the allocation was successful)15 return (a pointer to the memory);16 // allocation was unsuccessful; find out what the17 // current new-handling function is (see below)18 new_handler globalHandler = set_new_handler(0);19 set_new_handler(globalHandler);20 if (globalHandler) (*globalHandler)();21 else throw std::bad_alloc();22 }23 }

 

把请求0个byte当作请求1一个byte来进行处理的诡计看上去让人厌恶,但这是简单的实现并且合法,而且能够工作,无论如何,你对0个byte的请求会有多频繁呢?

对于伪代码中将new-handling函数指针设为null,然后迅速的将其复原的地方,你可能看上去比较怀疑。不幸的是,没有其他方法直接获得new-handling函数的指针,所以你必须调用set_new_handler来发现这个函数是什么。看上去粗糙但却是有效的,起码对于单线程来说是有效的。在多线程环境中,你可能需要某种类型的锁来安全的操作new-handling函数背后的(全局)数据结构。

Item 49中讨论过了,在operator new中包含一个无限循环,上面的代码中将其展示了出来;“while(true)”就 表示一个无限循环。跳出循环的唯一方法是成功的分配内存或者让new-handling函数做到Item 49中描述的事情中的其中一件:有更多的内存可供分配,安装一个不同的new-handler,卸载new-handler,抛出一个异常,这个异常要么继承自bad_alloc要么源于失败返回。现在你应该清楚为什么new-handler必须做到这些事情中的一件的了,如果做不到,operator new中的循环永远不会终止。

1.2 由继承导致的问题

许多人没有意识到operator new成员函数是要被派生类继承的。这可能会导致一些有趣的并发症。在上面的operator new的伪代码中,注意函数尝试分配size个bytes。这再合理不过了,因为这是传递到函数中的参数。然而,正如Item 50中解释的,实现一个自定义内存管理器的最一般的原因就是为特定类的对象进行内存分配优化,而不是为类或者它的任何派生类。也即是,我们为类X提供了一个operaor new,这个函数的行为是为大小正好为sizeof(X)的对象进行调整,即不大也不小。然而由于继承的存在,可能发生通过调用基类中的operator new来为派生类对象分配内存:

1 class Base {2 public:3 static void* operator new(std::size_t size) throw(std::bad_alloc);4 ...5 };6 class Derived: public Base // Derived doesn’t declare7 { ... }; // operator new8 9 Derived *p = new Derived;                             // calls Base::operator new!

 

 

如果基类中的operator new设计没有处理这种情况,处理它的最好的方法将对“错误”数量内存的请求丢弃掉,而是转而使用标准operator new来处理,就像下面这样:

 1 void* Base::operator new(std::size_t size) throw(std::bad_alloc) 2  3 { 4  5  6 if (size != sizeof(Base)) // if size is “wrong,” 7 return ::operator new(size); // have standard operator 8 // new handle the request 9 ... // otherwise handle10 // the request here11 }

 

“等一下”我听见你大叫,“你忘记检查病态但是可能发生的情况,也就是size为0的情况了!”事实上,我没有忘记。测试仍然在那里,只不过是将测试并入size同sizeof(size)的测试之中了。C++用神秘的方式进行工作,其中之一的方式就是规定所有独立对象的大小不能为0(见Item 39)。根据定义,sizeof(Base)永远不会为0,所以如果size为0,内存请求将由::operator new来处理,它会以一种合理的方式来处理这个请求。

 

1.3 定义operator new[]的约定

 

如果你想在一个类中控制数组的内存分配,你需要实现operator new的数组形式,operator new[]。(这个函数通常被叫做“array new”,因为很难确定“operator new[]”该如何发音)。如果你决定实现operator new[],记住所有你正在做的是分配一大块原生内存——你不能对不存在于数组中的对象做任何事情。事实上,你甚至不能确定数组将会有多少对象。首先,你不会知道每个对象有多大。毕竟,很有可能通过继承来调用基类的operator new[]去为派生类对象数组分配内存,派生类对象通常比基类对象要大。因此,你不能假设在Base::operator new[]内部被放入数组的对象的大小为sizeof(Base),这就意味着你不能假设数组中对象的数量为(请求的字节数)/sizeof(Base)。第二,传递给operator new[]的参数size_t有可能比填入对象的内存更多,因为正如Item 16中解释的,动态分配的数组有可能包含额外的空间来存放数组元素的数量。

 

2. 定义operator delete的约定

 

当实现operator new的时候需要遵守的约定就这么多。对于operator delete,事情更加简单。所有你需要记住的是C++总是保证delete null指针是安全的,所以你需要遵守这个规定。下面是实现非成员 operator delete的伪代码:

1 void operator delete(void *rawMemory) throw()2 {3 if (rawMemory == 0) return; // do nothing if the null4 // pointer is being deleted5 deallocate the memory pointed to by rawMemory;6 }

 

这个函数的成员函数版本也是简单的,但是你需要确保检查正在被delete的对象的size。假设你的属于类的operator new将对错误数量内存的请求转发给了::operator new,你同样得将对“错误大小”的delete请求转发给::operator delete:

 1 class Base { // same as before, but now 2 public: // operator delete is declared 3 static void* operator new(std::size_t size) throw(std::bad_alloc); 4 static void operator delete(void *rawMemory, std::size_t size) throw(); 5 ... 6 }; 7 void Base::operator delete(void *rawMemory, std::size_t size) throw() 8 { 9 10 if (rawMemory == 0) return; // check for null pointer11 12 if (size != sizeof(Base)) { // if size is “wrong,”13 14 15 ::operator delete(rawMemory); // have standard operator16 17 return;                                                                           // delete handle the request18 19 }                                                                                    20 21 deallocate the memory pointed to by rawMemory;      22 23 return;                                                                          24 25 }        

 

                                                                    

 有趣的是,如果要被delete的对象派生自于一个没有虚析构函数的基类,那么传递给operator delete的size_t值有可能是不正确的。这就有了足够的理由来把你的基类中的析构函数声明为虚函数,但是Item 7中描述了第二个可能更好的原因。现在你需要注意的是如果你在基类中忽略了虚析构函数,operator delete函数的工作就有可能不正确。

 

3. 总结

  •  operator new应该包含一个无限循环来尝试分配内存,如果不能满足对内存的请求应该调用new-handler,应该处理对0个byte的请求。类的特定版本应该处理比预期更大的内存块的请求。
  • operator delete中传递的指针如果是null,应该什么都不做。类特定版本需要处理比预期要大的内存块。


作者:HarlanC

博客地址:http://www.cnblogs.com/harlanc/
个人博客: http://www.harlancn.me/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接

如果觉的博主写的可以,收到您的赞会是很大的动力,如果您觉的不好,您可以投反对票,但麻烦您留言写下问题在哪里,这样才能共同进步。谢谢!

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

热门文章
  • 机房智能化温湿度解决方式之POE供电以太网温湿度传感器

    机房智能化温湿度解决方式之POE供电以太网温湿度传感器
    机房智能化温湿度解决方式之POE供电以太网温湿度传感器 北京盈创力和电子科技有限公司 智能型TCP网口温湿度记录仪 北京IP网络温湿度记录仪厂家,北京盈创力和 北京智能型TCP网口温湿度记录仪IP网络温湿度记录仪是一种新型的基于TCP/IP协议双绞线以太网标准温湿度采集模块,利用它可以实现现场温度值、相对湿度值的采集,同时利用其自身的RJ45通信接口可以方便地和机房监控主机或交换机集线器进行联网。 工作于-40℃~85℃工业级带...
  • Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering

    Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering
    Problem Statement 我们考虑一个具有马尔可夫性质、非线性、非高斯的状态空间模型(State Space Model):对于一个时间序列上的观测结果{yt,t∈N}\\{ y_t , t \\in N \\}{yt​,t∈N},我们认为每个观测结果yty_tyt​的生成依赖于一个无法直接观察的隐变量xt∈{xt,t∈N}x_t \\in \\{x_t , t \\in N \\}xt​∈{xt​,t∈N},即:p(...
  • HTTP状态保持的原理

    HTTP状态保持的原理
    a)在用户登录之后,浏览器返回响应的时候会在响应中添加上cookieb)浏览器接收到cookie之后会自动保存c)当用户再次请求同一服务器中的其他网页的时候,浏览器会自动带上之前保存的cookied)服务接收到请求之后可以请 request 对象中取到cookie 判断当前用户是否登录  Http是无状态的,就是连接时数据互通,关闭后...
  • Hive 系统函数及示例

    Hive 系统函数及示例
    查看所有系统函数 show functions; 函数分类 内置函数【系统函数】 数学函数: floor、round、ceil、cos、log2等 字符串函数: length、reverse、trim、lower、get_json_object、repeat等 收集函数: size 转换函数: cast 日期函数: year、month、datediff、date、date_add等 条件函数: coalesce、case…w...
  • CSRF的原理和防范措施

    CSRF的原理和防范措施
    a)攻击原理:i.用户C访问正常网站A时进行登录,浏览器保存A的cookieii.用户C再访问攻击网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数iii.而攻击网站B在访问网站A的时候,浏览器会自动带上网站A的cookieiv.所以网站A在接收到请求之后可判断当前用户是登录状态,所以...
标签列表