Effective C++ 条款52 写了placement new也要写placment delete

1. placement new和place ment delete指的是正常的operator new和operator delete的重载版本,所谓的正常的operator new和delete,指的是拥有以下正常签名式的版本:

void* operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void*) throw();  //global作用域中的正常签名式
void operator delete(void*,std::size_t) throw(); //class作用域中的正常签名式

由于C++允许对重载的operator new和operator delete添加额外参数,因而具有额外参数的operator和operator delete就是placement new和placement delete.众多placement new版本中比较有用的一个是"接受一个指针指向对象该被构造之处",其主要用途是接受一个指针,然后将其返回以供构造函数在其上构造对象,签名式为:

void* operator new(std::size_t,void* pMemory) throw();

这个版本的new已被纳入C++标准程序库,使用它需要#include<new>,通常所指的placement new指的就是这一版本.

2. 对于以下语句:

Widget* pw1=new Widget;  //Widget是一个类

共有两个函数被调用:一个是用以分配内存的operator new,另一个是用于构造Widget的Widget default构造函数.

假设第一个函数调用成功,第二个却抛出异常,那么步骤1所申请的内存必须被释放,否则就是内存泄露.这个任务客户端无法做到,因此由C++运行期系统来完成:运行期系统会调用步骤一所调用的相应operator delete版本.所谓"相应",与具有正常签名式的operator new相应的版本就是具有正常签名式的operator delete(1中所列),而对于placement new,"相应"指的是额外参数相同.

假设Widget的定义如下:

class Widget{
public:
    ...
    static void* operator new(std::size_t size,std::ostream& logStream) throw(std::bad_alloc); //placement operator new
    static void operator delete(void* pMemory,std::size_t size) throw();   //正常operator delete
    ...
};

如果对于以下语句:

Widget* pw=new (std::cerr) Widget;

如果该语句在Widget default构造函数中抛出异常,那么运行期系统有责任取消operator new的分配并恢复旧观,正如以上所言,它需要找到与

static void* operator new(std::size_t size,std::ostream& logStream) throw(std::bad_alloc);

相应的operator delete来执行该任务,然而并没有签名式为:

static void operator delete(std::size_t size,std::ostream& logStream) throw();

的operator delete.结果就是"如果一个带额外参数的operator new没有‘带相同额外参数‘的对应版operator delete,那么当new的分配动作需要取消并回复旧观时就没有任何operator delete会被调用",内存泄露也就不可避免.解决方法就是为Widget声明并定义一个与之前带额外参数的operator new对应的operator delete:

class Widget{
public:
    ...
    static void* operator new(std::size_t size,std::ostream& logStream) throw(std::bad_alloc); //placement operator new
    static void operator delete(void* pMemory,std::size_t size) throw();   //正常operator delete
    static void operator delete(std::size_t size,std::ostream& logStream) throw(); //对应的operator delete
    ...
};

这样改变之后,如果以下语句引发Widget构造函数抛出异常:

Widget* pw=new(std::cerr) Widget;

对应的placement delete会被调用.

需要注意的是,只有当抛出异常时,调用的才会是对应的placement delete,如果以上语句执行正常,那么执行:

delete pw;

调用的是正常版本的operator delete.

3. 此外,C++的名称遮掩规则也会导致未预期的后果,假设有一个Base类声明如下:

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

那么由于名称遮掩规则,Base以及其派生类将无法使用global作用域内的operator new:

//标准库global作用域内的operator new
void* operator new(std::size_t) throw(std::bad_alloc);
void* operator new(std::size_t,void*) throw();
void* operator new(std::size_t,const std::nothrow_t&) throw();

要使用这些形式,只需要令class专属版本调用global版本即可,同时必须注意operator new和operator delete的对应:

class StandardNewDeleteForms{
public:
    // normal new/delete
    static void* operator new(std::size_t size) throw(std::bad_alloc){
        return ::operator new(size);
    }
    static void operator delete(void* pMemory) throw(){
        return ::operator delete(pMemory);
    }
    //placement mew/delete
     static void* operator new(std::size_t size,void* ptr) throw(std::bad_alloc){
        return ::operator new(size,ptr);
    }
    static void operator delete(void* pMemory,void* ptr) throw(){
        return ::operator delete(pMemory,ptr);
    }
    //nothrow new/delete
    static void* operator new(std::size_t size,const std::nothrow_t& nt) throw(std::bad_alloc){
        return ::operator new(size,nt);
    }
    static void operator delete(void* pMemory,const std::nothrow_t& nt) throw(){
        return ::operator delete(pMemory);
    }
    ...
};

凡是想以自定义形式扩充标准形式的客户,可利用继承机制及using声明式取得标准形式:

class Widget:public StandardNewDeleteForms{
public:
    using StandardNewDeleteForms::operator new;
    using StandardNewDeleteForms::operator delete;
    static void* operator new(std::size_t size,std::ostream& logStream) throw(std::bad_alloc);
    static void operator delete(void* pMemory,std::ostream& logStream) throw();
    ...
};

时间: 2024-07-29 20:25:29

Effective C++ 条款52 写了placement new也要写placment delete的相关文章

Effective C++ 条款52

写了placement new也要写placement delete 本文主要内容是对placement new 和 placement delete的介绍,以及在什么情况下使用placement new和placement delete. 对于语句Widget* pw=new Widget;来说,该语句做了两件事情,第一件事情是申请了内存区域:第二件事情是在该内存区上进行对象的构造,即调用构造函数.我们设想其中一种执行情况,当第一件事情完成,而第二件事情出现异常,那么我们应该怎么去处理,很显然

Effective C++ 条款 52:写了placement new也要写placement delete

(一) 当我们写下了下面这个语句: Widget* pw = new Widget; 这个时候,共有两个函数被调用:一个分配内存的operator new,另外一个是Widget的default构造函数. 假设第一个调用成功,第二个却抛出异常.步骤一所分配内存必须取消并恢复旧观,否则会造成内存泄漏.这时,客户没能力归还内存,因为Widget构造函数抛出异常,pw尚未被赋值,客户手上也就没有指针指向该被归还的内存. 这个时候,取消步骤一,并恢复旧观的责任就落到C++运行系统身上. 运行期系统会高兴

More Effective C++ 条款34 如何在一个程序中结合C++和C

1. C++和C混合使用的前提之一就是编译器产生兼容的目标文件(.lib和.dll等).所谓"兼容",指的是编译器在"预编译器相依的特性上"一致,如int和double大小,参数压栈机制等,只有在这个基础上才能讨论结合使用C++和C模块的问题. 2. 在1的基础上,要结合使用C++和C的模块,主要有以下几点需要注意: 1). name mangling(名称重整) Name mangling是C++用于支持函数重载的机制,它对重载的函数名称进行一定改变,使得每个函数

Effective C++ 条款四 确定对象被使用前已被初始化

1.对于某些array不保证其内容被初始化,而vector(来自STL)却有此保证. 2.永远在使用对象前初始化.对于无任何成员的内置类型,必须手工完成.      int x = 0;      const int * p = &x; 3.不要混淆赋值与初始化的区别.一般初始化在定义的时候一起进行.而赋值是在定义之后的动作.      比如说在某一个类中的构造函数中,函数的行为都是赋值操作,而非初始化操作.      一般来说,对象的成员变量的初始化动作发生在进入构造函数本体之前.所以,我们一

More Effective C++ 条款0,1

More Effective C++ 条款0,1 条款0 关于编译器 不同的编译器支持C++的特性能力不同.有些编译器不支持bool类型,此时可用 enum bool{false, true};枚举类型来模拟bool类型.这允许参数类型为int和bool的函数重载,但是这样做的缺陷是,对于内置的比较运算符,其仍返回int类型. f(int);f(bool); f(a < b); // 会调用f(int),但其实用户期望调用f(bool). 但是一旦改用支持bool类型的编译器,情况可能会发生改变

More Effective C++ 条款35 让自己习惯于标准C++ 语言

(由于本书出版于1996年,因此当时的新特性现在来说可能已经习以为常,但现在重新了解反而会起到了解C++变迁的作用) 1. 1990年后C++的重要改变 1). 增加了新的语言特性:RTTI,namespaces,bool,关键词mutable和explicit,enums作为重载函数之自变量所引发的类型晋升转换,以及"在class 定义区内直接为整数型(intergral) const static class members设定初值"的能力. 2). 扩充了Templates的特性

effective c++ 条款4 make sure that objects are initialized before they are used

1 c++ 类的数据成员的初始化发生在构造函数前 class InitialData { public: int data1; int data2; InitialData(int a, int b) { data1 = a: //this is assignment data2 = b; //this is assignment } /* InitialData(int a, int b):data1(a),data2(b) //this is initial {} */ } 2 不同cpp文

Effective C++ 条款3 尽可能用const

1. const可被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数本体.用const修饰指针,如果const出现在*之前,表明指针不能更改所指向的对象的内容,如果const出现在*之后,表明指针只能指向同一块内存.另外int const*p和const int*p含义相同.如果对象成员有普通指针,那么构造该类的一个const对象时,const修饰使得该指针只能指向同一块内存,但指针指向的内容可以改变. 2. 将某些东西声明为const可以帮助编译器侦测出错误用法. 3. 编译器强制实

effective c++ 条款13 use object to manage resources.

请求的系统资源需要最终还回系统,为了避免遗忘返还这个动作,可以利用析构函数在object销毁时自动调用的特点来实现. 简单说就是用object来管理资源. 以内存资源为例 class Investment {}; Investment* creatInvestment(){...} // factory function to produce investment object void main() { Investment* pInv = creatInvestment();//call t