Item 51:写new和delete时请遵循惯例

Item 51: Adhere to convention when writing new and delete.

Item 50介绍了怎样自己定义newdelete但没有解释你必须遵循的惯例。
这些惯例中有些并不直观,所以你须要记住它们!

  • operator
    new
    须要无限循环地获取资源。假设没能获取则调用”new handler”。不存在”new handler”时应该抛出异常。
  • operator
    new
    应该处理size
    == 0
    的情况。
  • operator
    delete
    应该兼容空指针。
  • operator
    new/delete
    作为成员函数应该处理size
    > sizeof(Base)
    的情况(由于继承的存在)。

外部operator new

Item 49指出了怎样将operator
new
重载为类的成员函数,在此我们先看看怎样实现一个外部(非成员函数)的operator
new
: operator
new
应当有正确的返回值。在内存不足时应当调用”new handler”,请求申请大小为0的内存时也能够正常运行。避免隐藏全局的(”normal form”)new

  • 给出返回值非常easy。

    当内存足够时。返回申请到的内存地址;当内存不足时。依据Item
    49
    描写叙述的规则返回空或者抛出bad_alloc异常。

  • 每次失败时调用”new handler”,并反复申请内存却不太easy。仅仅有当”new handler”为空时才应抛出异常。
  • 申请大小为零时也应返回合法的指针。同意申请大小为零的空间确实会给编程带来方便。

考虑到上述目标,一个非成员函数的operator
new
大致实现例如以下:

void * operator new(std::size_t size) throw(std::bad_alloc){
    if(size == 0) size = 1;
    while(true){
        // 尝试申请
        void *p = malloc(size);

        // 申请成功
        if(p) return p;

        // 申请失败,获得new handler
        new_handler h = set_new_handler(0);
        set_new_handler(h);

        if(h) (*h)();
        else throw bad_alloc();
    }
}
  • size
    == 0
    时申请大小为1看起来不太合适,但它很easy并且能正常工作。况且你不会常常申请大小为0的空间吧?
  • 两次set_new_handler调用先把全局”new
    handler”设置为空再设置回来,这是由于无法直接获取”new handler”,多线程环境下这里一定须要锁。
  • while(true)意味着这可能是一个死循环。所以Item
    49
    提到,”new handler”要么释放很多其它内存、要么安装一个新的”new handler”,假设你实现了一个没用的”new handler”这里就是死循环了。

成员operator new

重载operator
new
为成员函数一般是为了对某个特定的类进行动态内存管理的优化,而不是用来给它的子类用的。 由于在实现Base::operator
new()
时,是基于对象大小为sizeof(Base)来进行内存管理优化的。

当然。有些情况你写的Base::operator
new
是通用于整个class及其子类的,这时这一条规则不适用。

class Base{
public:
    static void* operator new(std::size_t size) throw(std::bad_alloc);
};
class Derived: public Base{...};

Derived *p = new Derived;       // 调用了 Base::operator new !

子类继承Base::operator
new()
之后,由于当前对象不再是如果的大小。该方法不再适合管理当前对象的内存了。

能够在Base::operator
new
中推断參数size。当大小不为sizeof(Base)时调用全局的new

void *Base::operator new(std::size_t size) throw(std::bad_alloc){
    if(size != sizeof(Base)) return ::operator new(size);
    ...
}

上面的代码没有检查size
== 0
。这是C++奇妙的地方,大小为0的独立对象会被插入一个char(见Item
39
)。 所以sizeof(Base)永远不会是0,所以size
== 0
的情况交给::operator
new(size)
去处理了。

这里提一下operator
new[]
。它和operator
new
具有相同的參数和返回值, 要注意的是你不要如果当中有几个对象。以及每一个对象的大小是多少,所以不要操作这些还不存在的对象。

由于:

  1. 你不知道对象大小是什么。上面也提到了当继承发生时size不一定等于sizeof(Base)

  2. size实參的值可能大于这些对象的大小之和。由于Item
    16
    中提到。数组的大小可能也须要存储。

外部operator delete

相比于new,实现delete的规则要简单非常多。唯一须要注意的是C++保证了delete一个NULL总是安全的,你尊重该惯例就可以。

相同地,先实现一个外部(非成员)的delete

void operator delete(void *rawMem) throw(){
    if(rawMem == 0) return;
    // 释放内存
}

成员operator delete

成员函数的delete也非常easy,但要注意假设你的new转发了其它size的申请,那么delete也应该转发其它size的申请。

class Base{
public:
    static void * operator new(std::size_t size) throw(std::bad_alloc);
    static void operator delete(void *rawMem, std::size_t size) throw();
};
void Base::operator delete(void *rawMem, std::size_t size) throw(){
    if(rawMem == 0) return;     // 检查空指针
    if(size != sizeof(Base)){
        ::operator delete(rawMem);
    }
    // 释放内存
}

注意上面的检查的是rawMem为空,size是不会为空的。

事实上size实參的值是通过调用者的类型来推导的(假设没有虚析构函数的话):

Base *p = new Derived;  // 如果Base::~Base不是虚函数
delete p;               // 传入`delete(void *rawMem, std::size_t size)`的`size == sizeof(Base)`。

假设Base::~Base()声明为virtual,则上述size就是正确的sizeof(Derived)
这也是为什么Item 7指出析构函数一定要声明virtual

本文地址:http://harttle.com/2015/09/20/effective-cpp-51.html

时间: 2024-10-24 09:59:01

Item 51:写new和delete时请遵循惯例的相关文章

条款八: 写operator new和operator delete时要遵循常规

自己重写operator new时(条款10解释了为什么有时要重写它),很重要的一点是函数提供的行为要和系统缺省的operator new一致.实际做起来也就是:要有正确的返回值:可用内存不够时要调用出错处理函数(见条款7):处理好0字节内存请求的情况.此外,还要避免不小心隐藏了标准形式的new,不过这是条款9的话题. 有关返回值的部分很简单.如果内存分配请求成功,就返回指向内存的指针:如果失败,则遵循条款7的规定抛出一个std::bad_alloc类型的异常.operator new实际上会不

Effective C++ 条款51 编写new和delete时需固守常规

1. 实现定制的operator new和operator delete需要满足一定的要求. 以operator new而言:实现一致性operator new必须返回正确的值;内存不足时必得调用new-handling函数;必须有对付零内存需求的准备;需避免不慎掩盖正常形式的new;如果有能力供应客户申请的内存,就返回一个指针指向该内存,反之就遵循条款49的规则并抛出bad_alloc异常;应该内含一个无限循环,知道成功分配内存或new-handling完成其功能...... 以上的要求中"必

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

Item 50中解释了在什么情况下你可能想实现自己版本的operator new和operator delete,但是没有解释当你实现的时候需要遵守的约定.遵守这些规则并不是很困难,但是它们其中有一些并不直观,所以知道这些规则是什么很重要. 1. 定义operator new的约定 1.1 约定列举 我们以operator new开始.实现一个一致的operator new需要有正确的返回值,在没有足够内存的时候调用new-handling函数(见Item 49),并且做好准备处理没有内存可分配

Effective C++ Item 46 需要类型转换时请为模板定义非成员函数

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:当我们编写一个 class template, 而它所提供之"与此 template 相关的"函数支持"所有参数之隐式类型转换"时,请将那些函数定义为 "class template内部的 friend 函数". 示例: template<typename T> class Rational{ public: Ration

我在写多语言支持时用到的东西

我在写多语言支持时用到的东西 絮叨絮叨:好久不来写了,竟然支持markdown 了. 我也是在项目里的wiki 里干刚接触了一些, 来这里也试试.然后悲催的发现,mac 电脑在markdown下直接上传图片有bug @2015-08-19 20:28:13.一会试一下链接版的吧. 我们的37度手环一不小心要卖到国外去了,自然要支持多国家多语言啦. 等卖到阿拉伯世界的时候,我会再补充RTL(Right To Left)相关的内容. 本文仅涉及安卓(Android)客户端的实现, 服务器后台的部分没

Effective C++:条款16:成对使用new和delete时要采取相同形式

(一) 先看下面的代码: string* stringArray = new std::string[100]; ... delete stringArray; 这样的做法是错误的,因为stringArray所含的100个string对象中的99个可能并没有被适当地删除,因为它们的析构函数很可能没有被调用. (二) 使用new时发生的事情: (1)内存被分配出来: (2)针对此内存会有一个或更多个构造函数被调用: 使用delete,也有两个动作: (1)针对此内存会有一个或更多个析构函数被调用:

条款16:成对使用new和delete时,采取相同的形式

问题聚焦: 我们都知道,new和delete要成对使用,但是有时候,事情往往不是按我们预期的那样发展. 对于单一对象和对象数组,我们要分开考虑. 遇到typedef时,也需要搞清楚,是单一对象类型还是对象数组类型. 来看一个例子: std::string* stringArray = new std::string[100]; ... delete stringArray; 问题:stringArray所含的100个string对象中的99个可能并没有被适当地删除,因为它们的析构函数很可能没有被

24、覆盖equals时请遵守通用约定

覆盖equals方法看似很简单,但是有许多覆盖方式会导致错误,并且后果非常严重.最容易的避免这类问题的方法就是不覆盖equals方法,这种情况下,每个实例都与它自身相等. 如果你必须覆盖equals方法,那么请遵循: 1.自反性.对于任何非null的引用值x,x.equals(x)必须返回true: 2.对称性.对于任何非null的引用值x和y,当且进党y.equalts(x)返回true时,x.equals(y)必须返回true: 3.传递性.对于任何非null的引用值x和y和z,如果x.eq

oracle Plsql 运行update或者delete时卡死问题解决的方法

oracle Plsql 运行update或者delete时 遇到过Plsql卡死问题或者导致代码运行sql的时候就卡死. 在开发中遇到此问题的时候,本来把sql复制出来,在plsql中运行,Sql本身拼写无误,可是出现plsql卡死的情况, 在代码中,运行sql的地方打断点debug,发现运行sql,仍然没有响应.经过网上查资料,推測导致这样的情况的原因是 可能在PLSQL Developer运行update时没有commit,oracle将该条记录锁住了. 能够通过下面办法解决: 先查询锁定