C++中new的用法

时间:2023-01-29 14:33:16

文章转载自http://blog.csdn.net/waken_ma/article/details/4007914

更多解释http://www.cnblogs.com/raichen/p/5808766.html

http://www.jb51.net/article/41331.htm


new的过程

当我们使用关键字new在堆上动态创建一个对象时,它实际上做了三件事:获得一块内存空间、调用构造函数、返回正确的指针。当然,如果我们创建的是简单类型的变量,那么第二步会被省略。假如我们定义了如下一个类A:

class A
{
int i;
public:
A(int _i) :i(_i*_i) {}
void Say() { printf(/"i=%d//n/", i); }
};

//调用new:
A* pa = new A(3);

那么上述动态创建一个对象的过程大致相当于以下三句话(只是大致上):

A* pa = (A*)malloc(sizeof(A));
pa->A::A(3);
return pa;


虽然从效果上看,这三句话也得到了一个有效的指向堆上的A对象的指针pa,但区别在于,当malloc失败时,它不会调用分配内存失败处理程序new_handler,而使用new的话会的。因此我们还是要尽可能的使用new,除非有一些特殊的需求。


在C++中一提到new,至少可能代表以下三种含义:new operator、operator new、placement new。

new operator的第一步分配内存实际上是通过调用operator new来完成的,这里的new实际上是像加减乘除一样的操作符,因此也是可以重载的。operator new默认情况下首先调

用分配内存的代码,尝试得到一段堆上的空间,如果成功就返回,如果失败,则转而去调用一个new_hander,然后继续重复前面过程。如果我们对这个过程不满意,就可以重载operator new,

来设置我们希望的行为。例如:

class A
{
public:
void* operator new(size_t size)
{
printf(/"operator new called//n/");
return ::operator new(size);
}
};

A* a = new A();

这里通过::operator new调用了原有的全局的new,实现了在分配内存之前输出一句话。全局的operator new也是可以重载的,但这样一来就不能再递归的使用new来分配内存,而只能使用malloc了:

void* operator new(size_t size)
{
printf(/"global new//n/");
return malloc(size);
}
相应的,delete也有delete operator和operator delete之分,后者也是可以重载的。并且,如果重载了operator new,就应该也相应的重载operator delete,这是良好的编程习惯。

new的第三种形态——placement new是用来实现定位构造的,因此可以实现new operator三步操作中的第二步,也就是在取得了一块可以容纳指定类型对象的内存后,在

这块内存上构造一个对象,这有点类似于前面代码中的“p->A::A(3);”这句话,但这并不是一个标准的写法,正确的写法是使用placement new:


#include <new.h>

void main()
{
char s[sizeof(A)]; //栈空间
A* p = (A*)s;
new(p) A(3); //p->A::A(3);
p->Say();
}

对头文件<new>或<new.h>的引用是必须的,这样才可以使用placement new。这里“new(p) A(3)”这种奇怪的写法便是placement new了,它实现了在指定内存地址上用指定类型的构造函数来构造一个对象的功能,后面A(3)就是对构造函数的显式调用。这里不难发现,这块指定的地址既可以是栈,又可以是堆,placement对此不加区分。

如果是像上面那样在栈上使用了placement new,则必须手工调用析构函数,这也是显式调用析构函数的唯一情况:
p->~A();
当我们觉得默认的new operator对内存的管理不能满足我们的需要,而希望自己手工的管理内存时,placement new就有用了。STL中的allocator就使用了这种方式,借助placement new来实现更灵活有效的内存管理。处理内存分配异常

正如前面所说,operator new的默认行为是请求分配内存,如果成功则返回此内存地址,如果失败则调用一个new_handler,然后再重复此过程。于是,想要从operator new的执行过程中返回,则必然需要满足下列条件之一:

(1)分配内存成功

(2)new_handler中抛出bad_alloc异常

(3)new_handler中调用exit()或类似的函数,使程序结束

于是,我们可以假设默认情况下operator new的行为是这样的:

void* operator new(size_t size)
{
void* p = null
while(!(p = malloc(size)))
{
if(null == new_handler)
throw bad_alloc();
try
{
new_handler();
}
catch(bad_alloc e)
{
throw e;
}
catch(…)
{}
}
return p;
}

在默认情况下,new_handler的行为是抛出一个bad_alloc异常,因此上述循环只会执行一次。但如果我们不希望使用默认行为,可以自定义一个new_handler,并使用std::set_new_handler函数使其生效。在自定义的new_handler中,我们可以抛出异常,可以结束程序,也可以运行一些代码使得有可能有内存被空闲出来,从而下一次分配时也许会成功,也可以通过set_new_handler来安装另一个可能更有效的new_handler。例如:

void MyNewHandler()
{
printf(“New handler called!//n”);
throw std::bad_alloc();
}

std::set_new_handler(MyNewHandler);