c++ new delete

当创建一个c++对象时,会发生两件事:

(1)为对象分配内存。

(2)调用构造函数初始化这块内容。

这块内存可以位于3个区域:静态存储区、栈、堆。

operator new

当用new创建一个对象时,它将在堆里为对象分配内存(使用“operator new()”)并为这块内存调用构造函数。

MyType * fp = new MyType;

operator delete

与new结合使用。delete先调用析构函数,然后释放内存。

delete fp;

同一指针delete两次会产生未定义行为(程序可能崩溃),所以在delete某一指针之后最好将其置0,因为delete零值指针是安全的(其行为是什么都不做)。

当在栈中创建对象时,编译器知道其确切的类型、数量、所占内存的大小、生存期,这些信息将被准确的记录在目标代码中。当在堆中创建对象时(称为动态创建对象),需要在运行时动态搜索一块足够大的内存以满足要求,并将这块内存的大小和地址记录下来,防止它被再次占用,同时在释放内存时系统能够知道该释放多大的内存。所以在堆中创建对象和在栈中创建对象相比,系统所花费的内存管理的开销要大。

delete void* 可能会造成内存泄露

delete void*指针只会释放当前对象所占用的内存,不会调用其析构函数。下面的代码将会造成memory leak。

class Object 
{
    void *data; // Some storage
    const int size;
    const char id;
public:
    Object(int sz, char c) : size(sz), id(c) 
    {
        data = new char[size];
        cout << "Constructing object " << id 
            << ", size = " << size << endl;
    }
    ~Object() 
    { 
        cout << "Destructing object " << id << endl;
        delete [] data; 
    }
};

int main()
{
    void *obj = new Object(40, 'a');
    delete obj;
}

用于数组的new []delete []

MyType *fp = new MyType[100];

这里要求MyType拥有一个工作良好的默认构造函数(如果程序员没有编写默认构造函数,编译器会自动生成一个)。

delete [] fp;

空方括号的作用是告诉编译器,应该将创建数组时存放在某处的对象数量取回,并为数组的所有对象调用析构函数。

new-handler

当operator new()分配内存失败时,一个称为new-handler的特殊函数将会被调用。new-handler的默认动作是产生一个异常,我们可以自定义一个新的new-handler函数。

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

int count = 0;

void out_of_memory() 
{
    cerr << "memory exhausted after " << count 
        << " allocations!" << endl;
    exit(1);
}

int main() 
{
    set_new_handler(out_of_memory);
    while(1) 
    {
        count++;
        new int[1000]; // Exhausts memory
    }
}

new-handler函数不含参数且返回值为void。可以编写更为复杂的new-handler,甚至可以回收内存(把new-handler实现为一个garbage collector)。

new-handler会试着调用operator new(),如果已经重载了operator new(),则new-handler将不会按默认调用。

重载newdelete

new表达式先使用operator new()分配内存,然后调用构造函数;delete表达式先调用析构函数,然后调用operator delete()释放内存。

可以重载operator new()和operator delete()以改变原有的内存分配和释放方法。重载的目的通常是为了加快内存分配速度或者减少内存碎片等等。

当重载operator new()时,同时也必须决定当内存分配失败时去做什么。返回0?、写一个调用new-handler的循环?、重新尝试分配?、(典型做法)产生一个bad_alloc异常?。

可以重载全局new和delete,也可以重载属于特定类的new和delete。

重载全局newdelete

重载全局new和delete是很极端的做法,这将导致默认版本完全不能被访问。

重载的operator new()有一个size_t参数,代表要分配的内存的字节大小。返回类型为void *,而不是指向特定类型的指针。因为operator new()仅仅是分配内存,并不能完成对象建立。对象建立需要调用构造函数,这是编译器确保完成的动作,不在我们的控制范围之内。

重载的operator delete()有一个void *参数,指向要释放的内存。返回类型为void。

#include <cstdio>
#include <cstdlib>
using namespace std;

void * operator new(size_t sz) 
{
    printf("operator new: %d Bytes\n", sz);
    void *m = malloc(sz);
    if(!m) puts("out of memory");
    return m;
}

void operator delete(void *m) 
{
    puts("operator delete");
    free(m);
}

class S 
{
    int i[100];
public:
    S() { puts("S::S()"); }
    ~S() { puts("S::~S()"); }
};

int main() 
{
    puts("creating & destroying an int");
    int *p = new int(47);
    delete p;
    puts("\n");

    puts("creating & destroying an s");
    S *s = new S;
    delete s;
    puts("\n");

    puts("creating & destroying S[3]");
    S *sa = new S[3];
    delete [] sa;
    puts("\n");
}

在重载的operator new()和operator delete()中使用的是printf()和puts()而不是iostream,因为当创建一个iostream对象(如全局cin、cout、cerr),会调用全局new分配内存,这样将造成死循环。

通过示例程序的输出结果可以看出,无论是创建内置数据类型对象,还是自定义类类型对象(及数组),都使用了重载的全局new和delete。

对于一个类重载newdelete

当为一个类重载new和delete时,实际上是创建其static成员函数operator new()和operator delete()(即使没有显示写出static关键字)。当编译器看到使用new创建其类型对象时,会选择使用其成员版本的operator new()而不是全局new。

#include <cstddef> // size_t
#include <fstream>
#include <iostream>
#include <new>
using namespace std;
ofstream out("Framis.out");

class Framis 
{
    enum { sz = 10 };
    char c[sz]; // To take up space, not used
    static unsigned char pool[];
    static bool alloc_map[];
public:
    enum { psize = 10 };  // frami allowed
    Framis() { out << "Framis()\n"; }
    ~Framis() { out << "~Framis() ... "; }
    void * operator new(size_t) throw(bad_alloc);
    void operator delete(void *);
};

unsigned char Framis::pool[psize * sizeof(Framis)];
bool Framis::alloc_map[psize] = {false};

// Size is ignored -- assume a Framis object
void * Framis::operator new(size_t) throw(bad_alloc) 
{
    for(int i = 0; i < psize; i++)
        if(!alloc_map[i]) 
        {
            out << "using block " << i << " ... ";
            alloc_map[i] = true; // Mark it used
            return pool + (i * sizeof(Framis));
        }

    out << "out of memory" << endl;
    throw bad_alloc();
}

void Framis::operator delete(void *m) 
{
    if(!m) return; // Check for null pointer
    // Assume it was created in the pool
    // Calculate which block number it is:
    unsigned long block = (unsigned long)m - (unsigned long)pool;
    block /= sizeof(Framis);
    out << "freeing block " << block << endl;
    // Mark it free:
    alloc_map[block] = false;
}

int main() 
{
    Framis* f[Framis::psize];

    try 
    {
        for(int i = 0; i < Framis::psize; i++)
            f[i] = new Framis;
        new Framis; // Out of memory
    } 
    catch(bad_alloc) 
    {
        cerr << "Out of memory!" << endl;
    }

    delete f[5];
    f[5] = 0;

    // Use released memory(delete f[5]):
    Framis *x = new Framis;
    delete x;

    for(int j = 0; j < Framis::psize; j++)
        delete f[j]; // Delete f[10] OK
}

Framis.out输出结果如下:

using block 0 ... Framis()
using block 1 ... Framis()
using block 2 ... Framis()
using block 3 ... Framis()
using block 4 ... Framis()
using block 5 ... Framis()
using block 6 ... Framis()
using block 7 ... Framis()
using block 8 ... Framis()
using block 9 ... Framis()
out of memory
~Framis() ... freeing block 5
using block 5 ... Framis()
~Framis() ... freeing block 5
~Framis() ... freeing block 0
~Framis() ... freeing block 1
~Framis() ... freeing block 2
~Framis() ... freeing block 3
~Framis() ... freeing block 4
~Framis() ... freeing block 6
~Framis() ... freeing block 7
~Framis() ... freeing block 8
~Framis() ... freeing block 9

注意:class Framis重载的new和delete,其派生类是不能使用的。也就是派生类不会继承基类重载的new和delete。

为类重载 operator new []operator delete []

如果只为类重载了operator new()和operator delete(),当创建这个类的对象数组时,还是会使用全局operator new ()。可以为类重载operator new []和operator delete []以控制其对象数组的内存分配。

#include <new> // size_t definition
#include <fstream>
using namespace std;
ofstream trace("ArrayOperatorNew.out");

class Widget 
{
    enum { sz = 10 };
    int i[sz];
public:
    Widget() { trace << "*"; }
    ~Widget() { trace << "~"; }
    void * operator new(size_t sz) 
    {
        trace << "Widget::new: "
            << sz << " bytes" << endl;
        return ::new char[sz];
    }
    void operator delete(void *p) 
    {
        trace << "Widget::delete" << endl;
        ::delete [] p;
    }
    void * operator new[](size_t sz) 
    {
        trace << "Widget::new[]: "
            << sz << " bytes" << endl;
        return ::new char[sz];
    }
    void operator delete[](void *p) 
    {
        trace << "Widget::delete []" << endl;
        ::delete [] p;
    }
};

int main() 
{
    trace << "new Widget" << endl;
    Widget *w = new Widget;
    trace << "\ndelete Widget" << endl;
    delete w;

    trace << "\nnew Widget[25]" << endl;
    Widget *wa = new Widget[25];
    trace << "\ndelete [] Widget" << endl;
    delete [] wa;
}

跟踪文件的输出信息:

new Widget
Widget::new: 40 bytes
*
delete Widget
~Widget::delete

new Widget[25]
Widget::new[]: 1004 bytes
*************************
delete [] Widget
~~~~~~~~~~~~~~~~~~~~~~~~~Widget::delete[]

当创建单个Widget对象时,首先调用类重载的operator new()函数分配40个字节大小的内存。然后调用构造函数(输出*)。删除单个对象时,先调用析构函数(输出~),再调用类重载的operator delete()。

当创建Widget对象数组时,首先调用类重载的operator new[]。需要分配的内存额外多出4个字节,这4个字节是系统用来存放数组信息的,比如数组中对象的数量。然后调用25次Widget构造函数。当删除对象数组时(delete [] Widget),方括号告诉编译器要删除的是一个对象数组。编译器会先获取那4个字节所存储的数组信息,得到对象数量并多次(25)调用析构函数,然后调用类重载的operator delete[]。

构造函数调用

如果使用new操作符时内存分配失败,则编译器不会调用构造函数。

#include <iostream>
#include <new> // bad_alloc definition
using namespace std;

class NoMemory 
{
public:
    NoMemory() { cout << "NoMemory::NoMemory()" << endl; }

    void * operator new(size_t sz) throw(bad_alloc)
    {
        cout << "NoMemory::operator new" << endl;
        throw bad_alloc(); // "Out of memory"
    }
};

int main() 
{
    NoMemory *nm = 0;

    try 
    {
        nm = new NoMemory;
    } 
    catch(bad_alloc) 
    {
        cerr << "Out of memory exception" << endl;
    }

    cout << "nm = " << nm << endl;
}

程序输出:

placement newplacement delete

placement new(定位new)在已分配的原始内存中初始化一个对象。它与new的其他版本的不同之处在于,它不分配内存。相反,它接受指向已分配但未构造的内存的指针,并在该内存中初始化一个对象。placement new表达式能够在特定的、预分配的内存地址上构造一个对象。

#include <cstddef> // size_t
#include <iostream>
using namespace std;

class X 
{
    int i;
public:
    X(int ii = 0) : i(ii) { cout << "this = " << this << endl; }
    ~X() { cout << "X::~X(): " << this << endl;}

    void * operator new(size_t, void *loc) 
    { return loc; }
};

int main() 
{
    int l[10];
    cout << "l = " << l << endl;
    X *xp = new(l) X(47); // X at location l
    xp->X::~X(); // Explicit destructor call
    // ONLY use with placement!
}

输出结果:

【学习资料】 《Thinking in c++》

posted on 2013-03-17 20:10  zhuyf87  阅读(655)  评论(0编辑  收藏

导航