Effective c++ 8 49...52

手工管理内存 heap资源

软件使用内存的行为特征---手动定/修改分配和归还内存的工作。

C++内存管理例程的行为?

operator new, operator delete, +new-handler, operator new[], operator delete[]

注:不负责STL容器的HEAP管理

多线程环境下的内存管理?

heap——可被改动的全局性资源。race condition(竞速状态)出现 大家都同时访问一个资源,只拼速度,不稳定。

可改动的static数据。thread-aware(线程感知)程序员 同步控制(synchronization),无锁算法 ,精心防止并发访问(concurrent access)?

49*了解new-handle的行为

operator new无法满足某一种内存分配需求时,会怎么做呢?

1、调用一个客户指定的错误处理函数——一个new-handler。

2、抛出异常bad_alloc(【旧】返回一个null指针)。

客户如何指定的?

#ifdef _M_CEE_PURE
typedef void (__clrcall * new_handler) ();
#else
typedef void (__cdecl * new_handler) ();//new_handler是没有参数也不返回任何东西的函数指针
#endif
_CRTIMP2 new_handler __cdecl set_new_handler(_In_opt_ new_handler)    _THROW0();    // establish alternate new handler//获得一个new_handler并返回一个new_handler的函数。参数指向operater new无法分配足够内存时应该调用的new-handler函数。返回被替换的那个new-handler函数。
_STD_END    <new>头文件

客户定义的错误处理函数new handler应该什么样子?

举例:

void outOfMen()//没有参数也不返回任何值的函数
{
    std::cerr<<"Unable to satisfy request for memory\n";//提示
    //1让更多内存可用——程序开始执行就分配一块内存,当new-handler第一次被调用,将它们释还给程序使用。
    //2调用std::set_new_handler呼叫另一个new_handler函数。或者直接修改自身,比如改static数据,全局数据,namespace数据。
    //3抛出bad_alloc异常(或派生),异常不会被operatornew捕捉,会被传播到内存索求处。
    //4不返回,调用abort或者exit终止程序。
    std::abort();//终止程序
}
int main()
{
    std::set_new_handler(outOfMen);//登录处理函数
    int* pBigDataArray = new int[1000000000L];
    return 0;
}

第二个话题:实现类自己的new_handler:new类对象的时候,出问题时,调用本类自己的new_handler。

技术思路:Widget类的operator new操作

1、 把Widget的处理函数登录

2、 把 登录返回值——旧的处理函数,存起来(资源方式)

3、 分配内存

4、 不论正确与否,结束operator new操作之前,(资源类析构函数完成)把旧的处理函数登录,恢复全局之前的状态。

//*******.h********
#include <iostream>

class Widget{
public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static void* operator new(std::size_t size) throw(std::bad_alloc);
private:
    static std::new_handler currentHandler;
};


class NewHandlerHolder//2 把登录返回值——旧的处理函数,存起来(资源方式)
{
public:
    explicit NewHandlerHolder(std::new_handler nh ):handler(nh){}
    ~NewHandlerHolder(){std::set_new_handler(handler);}//4 不论正确与否,结束operator new操作之前,(资源类析构函数完成)把旧的处理函数登录,恢复全局之前的状态。
private:
    std::new_handler handler;    //记录handler
    NewHandlerHolder(const NewHandlerHolder&);            //阻止copying
    NewHandlerHolder operator=(const NewHandlerHolder&);
};

//*******.cpp********
#include "template1.h"

std::new_handler Widget::currentHandler = 0;
std::new_handler Widget::set_new_handler( std::new_handler p ) throw()
{
    std::new_handler oldHandler = currentHandler;
    currentHandler = p;//1把Widget的处理函数登录
    return oldHandler;
}

void*  Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
    NewHandlerHolder h(std::set_new_handler(currentHandler));//1把Widget的处理函数登录//2 把登录返回值——旧的处理函数,存起来(资源方式)
//所谓资源方式:RAII,创建对象即赋值,撤销即销毁。
return ::operator new(size);//3分配内存 } //*******main******** #include "template1.h" void outOfMem() { std::cerr<<"Widget outOfMen"; std::abort(); } int main() { Widget::set_new_handler(outOfMem); Widget* pw = new Widget; return 0; }

 第三个话题:什么类都可以用这套技术的。复用。

1、mixin  base class 实现第二个话题中功能的类作为模板类,基类。事实上那个T并未在定义类的函数中使用。用处请看2。

template<typename T>
class NewHandlerSupport{ //【NewHandlerSupport<T>】
public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    //……其他的operator new版本
private:
    static std::new_handler currentHandler;
};


class NewHandlerHolder//hold old 资源 【未改变】
{
public:
    explicit NewHandlerHolder(std::new_handler nh ):handler(nh){}
    ~NewHandlerHolder(){std::set_new_handler(handler);}
private:
    std::new_handler handler;    //记录handler
    NewHandlerHolder(const NewHandlerHolder&);            //阻止copying
    NewHandlerHolder operator=(const NewHandlerHolder&);
};

//*******.cpp******* 【NewHandlerSupport<T>】
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;

template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler( std::new_handler p ) throw()
{
    std::new_handler oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;
}

template<typename T>
void*  NewHandlerSupport<T>::operator new(std::size_t size) throw(std::bad_alloc)
{
    NewHandlerHolder h(std::set_new_handler(currentHandler));
    return ::operator new(size);
}

2.CRTP (怪异循环模板 curiously recurring template pattern)——Do It For Me

class Widget:public NewHandlerSupport<Widget>//每一个T一个static 的currentHandler【目的】
{
//和先前一样,但不必声明set_new_handler和operator new,已经继承来了。
//【目的】继承自NewHandlerSupport<Widget>使得Widget类拥有自己的static 的currentHandler。
};

50*什么时候需要手工管理内存?

见书中列举。

一个好的内存管理器。 Booth的Pool程序库——分配大量小型对象。

一个小例子:

static const int signature = 0xDEADBEEF;
typedef unsigned char Byte;
//手工operator new 实现内存前后有标志
void* operator new( std::size_t size)
{
    using namespace std;
    size_t realSize = size + 2*sizeof(int);//用户所需内存再增加我们两个标志的内存

    void* pMem = malloc(realSize);//malloc保证对齐安全
    if (!pMem)    throw bad_alloc();
    
    //将signature写入内存的最前和最后段落
     *( static_cast<int*>(pMem) )= signature;//最前面一个int的内存给了signature用
     *( reinterpret_cast<int*>( static_cast<Byte*>(pMem)
         +realSize-sizeof(int) ) )= signature;//最后面一个int的内存给了signature用
    
     //返回指针 指向前面sinature后内存位置
     return static_cast<Byte*>(pMem) + sizeof(int);    //不确定对齐,不安全
}

51*编写operator new和operator delete需要固守的常规。

1、operator new 中应包含一个无限循环,并在其中尝试分配内存。如果成功返回指针,如果失败调用new-handler函数或者抛出异常。

2、operator new 应有能力处理0byte申请。

void* operator new( std::size_t size )
{
    using namespace std;
    if ( size == 0 )//2、operator new 应有能力处理0byte申请
    {
        size = 1;
    }

    while (true)//1、operator new 中应包含一个无限循环,并在其中尝试分配内存。如果成功返回指针,如果失败调用new-handler函数或者抛出异常。
    {
        尝试分配size byte;
        if (分配成功)
            return(一个指针,指向分配的来的内存);
        
        //分配失败:找出目前的new-handler函数
        new_handler globalHandler = set_new_handler(0);
        set_new_handler(globalHandler);

        if (globalHandler)//有new-handler函数
            (*globalHandler)()
        else//没有
            throw std::bad_alloc();
    }
}

3、Class专属版本operator new 应处理“比正确大小更大的(错误)申请”

void* BaseClass::operator new(std::size_t size)
{
    if (size != sizeof(BaseClass))//如果大小错误
        return ::operator new(size);//起用标准new实现
    ……//否则这里实现
}

写operator new[]时要做的唯一的事:分配一块raw memeory(未加工内存)。

4、operator delete 应在收到null指针时不做任何事。“C++保证删除null指针永远安全”

1 void* operator delete(void* rawMemory)
2 {
3     if (rawMemory ==0 ) return;//如果将被删除的是个null指针,什么都不做
4     //否则这里归还rawMemory所指内存
5 }

5、Class专属版本operator delete应处理“比正确大小更大的(错误)申请”

 1 void* BaseClass::operator delete(void* rawMemory,std::size_t size)//比常规的多一个size参数
 2 {
 3     if (rawMemory ==0 ) return;
 4     if (size != sizeof(BaseClass)){//如果大小错误
 5         ::operator delete(rawMemory);//起用标准delete实现
 6         return;
 7     }
 8     ……//这里实现归还rawMemory内存
 9     return;
10 }

 52*placement new和placement delete

placement new是什么:

是除了size那个参数之外,还有其它参数的operator new或者特指其它参数就是void*的operator new。

问题出在:

当调用完new紧接着调用的构造函数抛出异常时,客户代码中的delete无力释放这个new因为不知道指针,C++运行期系统需要调用operator new 对应的operator delete,或者调用参数与placement new对应的placement delete。如果找不到这种特别的delete,C++运行期系统就会什么都不做,那个new就无法被释放。

解决办法是:

提供一个正常的operator delete用于构造期间无任何异常抛出的情况;

提供一个placement delete用于期间有异常抛出的情况,额外参数与placement new一致即可。

第二个话题,placement new和delete掩盖了operator new 和delete的正常版本。什么时候会掩盖呢?比如,placement new 和delete定义在class内部,则这个class就看不到全局域里面常规的new和delete了。又比如派生类内定义的new和delete掩盖了他基类和全局域的所有版本。解决办法是让这个class继承自包含所有标准new和delete形式的类(一共三种,如下),然后用using声明露出其他版本。

 1 class StandardNewDeleteForms
 2 {
 3 public:
 4     //normal new/delete
 5     static void* operator new(std::size_t size) 
 6     { return ::operator new(size); }
 7     static void operator delete(void* pMemory)
 8     { ::operator delete(pMemory); }
 9 
10     //placement new/delete
11     static void* operator new(std::size_t size, void* ptr)
12     { return ::operator new(size,ptr); }
13     static void operator delete(void* pMemory,void* ptr)
14     { ::operator delete(pMemory,ptr);}
15 
16     //nothrow new/delete
17     static void* operator new(std::size_t size,const std::nothrow_t& nt)
18     { return ::operator new(size,nt); }
19     static void* operator delete(void* pMemory,const std::nothrow_t&)
20     {  ::operator delete(pMemory); }
21 };
 1 class Widget:public StandardNewDeleteForms{
 2 public:
 3     using StandardNewDeleteForms::operator new;
 4     using StandardNewDeleteForms::operator delete;//继承标准形式
 5     static void* operator new(std::size_t size,//再加自己的形式
 6         std::ostream& logStream);
 7     static void operator delete(void* pMemeory,
 8         std::ostream& logStream);
 9     //...
10 };

 

posted @ 2013-02-21 13:36  浑身胆满脸魄  阅读(167)  评论(0编辑  收藏  举报