8. 了解各种不同意义的new 和 delete

C++中关于new 的形态主要有三种:new operator,   operator new,   placement new

三者的用法有着不同,注意区别适用的条件:

new operator:

new 操作符,和C++语言其它操作符(如+, -, ->, ::....)一样,是由语言内建的,总是完成相同的事情,程序员不能改变其意义。如下为new的一种用法:

string *ps = new string("hazirguo");

它主要完成的任务包括三个方面:

  1. 分配足够的内存,用来放置某类型对象。上例中分配足够放置一个string对象的内存。
  2. 调用一个constructor,为分配的内存的对象初始化。上例中就调用string类的构造函数string::string( const string &s );
  3. 返回相应的指针。上例中返回指向新建string对象的指针。

注:new 和 sizeof 一样,是C++语言的关键字,不是函数,不能够被重载!

 

operator new:

上面new操作符完成的任务的第一点是调用函数执行分配内存动作,类似于C语言中malloc()函数的功能,你可以重载或重写此函数,改变其行为,这就是operator new 这个函数完成的功能。通常声明为:

void * operator new (size_t size);

参数size表示需要分配的内存的大小;函数的返回值为void *, 指向一块原始的、未初始化的内存。

该函数可以被重载,但第一参数的类型必须总是size_t。全局的operator new 的重载形式有多种,你也可以根据自己的需要写出自己的重载函数,如:

int *pnum = new int(5);   //使用new operator
//下面自己写一个operator new重载形式,初始化分配的内存
void *operator new (size_t size, int n)
{
    void *tmp = ::operator new(size);       //使用全局operator new函数的一种重载形式分配内存
    *static_cast<int *>(tmp) = n;
    return tmp;
}
//调用自己版本的重载函数
int *pnum = static_cast<int *>operator new(sizeof(int), 5);

placement new:

placement new 是operator new 下面的一个重载版本:

void * operator new (size_t size, void * location)
{
    return loaction;
}

完成在一个已经分配好的原始内存上构建一个新的对象并初始化。因为在已经分配好的内存空间上调用constructor进行对象初始化无意义,此时就用到placement new。使用前要加上头文件#include <new>,使用的方法如下:

CTest* pT = new(p) CTest(...);   //在p指向的内存中调用CTest的构造函数来初始化CTest对象,并返回此对象的指针。

 

举个实际的例子:
char *buffer = new char[sizeof("hazirguo")+1];         //事先分配内存
string *pch = new(buffer) string("hazirguo");          //完成对象的初始化

小结:

1. 如果希望将对象产生与堆上,使用new operator, 不但完成内存分配而且为该对象调用一个constructor。

2. 如果只打算分配内存,而不希望任何constructor被调用,请调用new operator函数。

3. 如果打算在已分配的内存中构造对象,请使用placement new。


同理,对于delete也有类似的用法:

delete operator:

对应与new operator的释放动作,既能够析构指针所指的对象,又能够释放被该对象占用的内存。如:

string *ps = new string("hazirguo"); 
...
delete ps;        //使用的是delete operator
//上句完成的功能近似于下面代码完成的功能:
ps->~string();          //1.调用对象的dtor
operator delete(ps);    //2.释放对象所占用的内存

operator delete:

完成内存释放动作的函数,通常声明如下:

void operator delete(void *memoryToBeDeallocated);
如果只打算处理原始的、未初始化的内存,应该完全回避new operator 和 delete operator,改调用operator new 取得内存并以operator delete归还给系统:
void *buffer = operator new (50 * sizeof(char));    //分配放置50个char型的内存空间,没有任何ctor
...
operator delete (buffer);                           //释放分配的内存,没有任何dtor

完成的功能类似与C语言中的malloc 和 free 函数的功能。

placement delete:

如果是使用placement new, 在某内存块中产生对象,应该避免对那块内存使用delete operator。因为delete operator会调用operator delete来释放内存,但该内存内含的对象最初并非是由operator new分配而来的。毕竟placement new只是返回它所接受的指针而已,并不知道那个指针从哪里而来。所以为了抵消该对象的constructor的影响,应该直接调用该对象的destructor。

举例:

下面一个实例来源于网络,感觉很能说明问题,摘于此供理解:

/* 
    F1 中的 new operator,他的行为就像F2中的 operator new 和 placement new 一样,
    也可以用F3中的方法达到相同的效果。
*/

#include <new>
#include <iostream>
using namespace std;

class CTest
{
public:
    CTest(int _x, int _y)
    {
        X = _x;
        Y = _y;
    }
    ~CTest()
    {
        X = 0;
        Y = 0;
    }
    void Test(char* sz)
    {
        printf("%s: X=%d Y=%d \n", sz, X, Y);
    }
    int X;
    int Y;
};

 
 /*   new operator  && delete operator   */

void F1()
{
    CTest* pT = new CTest(1, 1);       // new operator

    pT->Test("F1");

    delete pT;                         // delete operator
}

/*   operator new + placement new   &&  operator delete + placement delete     */

void F2()
{
    void*  p  = operator new(sizeof(CTest));  // operator new : 分配内存
    CTest* pT = new(p) CTest(2, 2);           // placement new: 构造对象
    
    pT->Test("F2");
    
    pT->~CTest();                            // 必须显示析构对象
    operator delete(pT);                     // operator delete: 释放内存
}


/*    also can implement like this    */

void F3()
{
    char*  p  = new char[sizeof(CTest)]; // new operator: char为内置类型,不会调用构造函数,相当于只分配内存
    CTest* pT = new(p) CTest(3, 3);      // placement new: 在这块内存上构造CTest对象
    
    pT->Test("F3");
    
    pT->~CTest();                        // 必须显示析构CTest对象
    delete [] p;                         // delete operator: char为内置类型,不会调用析构函数,相当于只释放内存
}

void main()
{
    F1();
    F2();
    F3();
}

前面都是对单对象,面对数组时,还有一些形式需要注意,如:

string *ps = new string[10];        //分配一个对象数组
此时,内存分配不再是operator new,而是名为operator new[]的函数负责,但是这个函数同样可以被重载。
(。。。此处待补充)
 
参考文献: 《More Effective C++ 35个改善编程与设计的有效方法 中文版》
posted @ 2012-04-15 14:15  hazir  阅读(470)  评论(0编辑  收藏  举报

一个代码可以创造一个世界,也可以毁灭一个世界!