#include <iostream>
#include <string>
using namespace std;
class A{
public:
A(){ cout << "这是A的构造器在执行!" << endl; }
~A(){ cout << "在执行A的析构器!" << endl; }
void *operator new(size_t size, string str)
{
cout << "new函数的尺寸为" << size << ",字符串是:" << str << endl;
return ::operator new(size); //在这里如果我们不调用标准的、全局的operator new,比如直接返回NULL,那么在后面的main函数中,delete p函数就不会得到执行,因为编译器会首先测试p指针是不是NULL值,是就跳过delete的执行
}
void operator delete(void *p)
{
cout << "delete版的重载函数在执行" << endl;
::operator delete(p);
}
private:
int n;
};
int main()
{
cout << "A的尺寸是:" << sizeof(A) << endl;
A *p = new("一个新的A对象") A; //在这里编译器会有扩展,先传递一个sizeof(A),再传递那个汉语字符串
delete p;
return 0;
}
在上面的例子中,如果我们能写下“ A* p = new A; ”【但现实情况下,我们这样写会编译错误,因为我们在A的类中已经给new定义了一个重载函数,而这个函数需要传递两个参数,而我们只传递过去一个sizeof(A)】,那么这里的new操作符会做以下这些事,首先是调用operator new类分配内存,接着再调用A类的constructor构造器,最后返回对应的指针。而我们这里的operator new和operator delete和C语言中的malloc和free差不多,只负责内存的分配与回收。事实上,在上面的代码运行时【我用的vs013】,结果显示调用了A的构造函数,但这并不是在operator new里面调用的,而是编译器的功劳】
那什么是placement new呢?首先说说placement new的好处,它适用于那些对效率要求很高【相比而言,单纯的new需要查找空间等】,能长时间运行不被打断的程序。再说说它的使用方法,以上面的自定义类A为例,如果我们是单个使用,那么只要预先在堆中开辟好内存【void *buf = new[sizeof(A)];】,然后再分配一下即可【A *p = new(buf) A;】,但是对于数组形式来说就稍微有些不同了:
void *buf = new[X*sizeof(A) + sizeof(int)];//其中,X是我们所需要的类对象数组的大小,而sizeof(int)则是一个多出来的用于存放数组大小的字节内存
然后分配时写【A *p = new(buf) A;】即可。。。
所谓的placement new,其实就是operator new的一个全局域的版本【这也是为什么我们不能在全局域中重载operator new函数的原因】,但与其它operator new不同的是,它不允许被用户自定义的版本的代替。对placement new的调用,一定要传递两个参数,首先是对象的大小,其次是void*类型的指针,但placement new会忽略掉第一个参数,既size_type,因为这个参数对它没啥用,但它一定会以void*形式返还第二个参数,既new出来的指针所指地址。看到这里也许有读者朋友会问,既然这个placement new做的是返回void*指针,那么我们何必调用它呢?我们在堆中开辟好一块内存之后,直接把这个内存起始地址赋值给我们的指针不就行了?嗯,对于那些内建类型如int、char等,确实如此,但对于那些自定义的有着constructor构造器的类类型来说,就不行了,因为我们除了给它们“安家置户”之外,还需要调用它们自带的构造器对它们constructor,让我们来看一下编译器扩展后的伪代码:
//经过编译器处理的伪代码
A *p = (A *)buf;//同上,buf是我们预先开辟好的起始内存地址
if( p != NULL )
p->A::A();//需要调用A的constructor
接下来,让我们来考虑下面的代码段:
void fooBar()
{
A *p = new(buf) A;
...//buf没有改变
//在这里该不该写【delete p;】?
A *p2 = new(buf) A;
}
如果placement new在原来已经存在的对象上构造一个新对象,而这个对象有一个析构器,那么这个析构器并不会被调用,调用它的析构器的方法是把那个指针delete掉,然和人如果我们真像注释里面那样写的话,那这样同时也会释放buf所指的内存,这并不是我们希望看到的。所以我们应该先明确调用一下A的析构器,然后再写【A *p2 = new(buf) A;】,不过据说,现在的c++ standard用一个placement operator delete矫正了这个错误:它会对类执行该类的析构器,而且不会释放对应的内存。
还有一个问题,关系到buf所表现出的真正类型,c++ standard说它要么指向相同类型的class,要么指向一块新鲜的内存,足够容纳该类型的对象,所以因为后者,derived class并不在被支持之列,对于derived class,其行为可能是不可控制的,因为如果一个派生类的占用内存如果比它的基类大,比如【A *P3 = new(buf) B;//B是A的derived class】,那么B的constructor很可能导致严重的破坏。
在placement operator new被引入到cfront2.0时,最隐晦问题是下面这个:
struct Base{int i; virtual void fun();}
struct Derived:Base { void fun(); }
void fooBar()
{
Base b;
b.fun();//Base::fun被调用
b.~Base();
new(&b) Derived;
b.fun();//在这里,哪个版本的fun被调用??
}
由于上述两个类有着相同的内存大小,所以把Derived对象放在为Base类中而配置的内存是安全的。但是,要支持这一点,就必须放弃对于“经由对象静态调用所有virtual函数”通常都会有的优化处理。所以最后结果是上面的placement operator new在c++ standard未能获得支持,因为上面代码中的问题并不能明确回答或定义,你可以说它调用的是base的fun,也可以说它调用的是derived里的fun。