C++中new有三种形式:new operator、operator new和placement new。
1. new operator
new operator就是我们平时使用的new表达式,来为特定类型分配内存,并在新分配的内存中构造该类型的一个对象。e.g.
Employee* sp = new Employee("001");
它实际上发生了三个步骤:
- 该表达式调用名为
operator new
的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象; - 运行该类型的一个构造函数,用指定初始化式构造对象;
- 返回指向新分配并构造的对象的指针。
伪代码就是:
4. Employee* sp = (Employee*)malloc(sizeof(Employee));
5. sp->Employee::Employee("001");
6. return sp;
当使用delete
表达式delete sp;
删除动态分配对象的时候,发生两个步骤:
- 对指向对象运行适当的析构函数;
- 通过调用名为
operator delete
的标准库函数释放该对象所用内存。
调用operator delete
函数不会运行析构函数,它只释放指定的内存。
new和delete表达式的行为是固定的,不能重定义的。
2. operator new
默认情况下,new表达式通过调用由标准库定义的operator new
版本分配内存。我们也可以通过定义自定义类型的名为operator new
和operator delete
的成员函数,管理用于自身类型的内存。
编译器看到类类型的new或delete表达式的时候,它查看该类是否有operator new
或operator delete
成员,如果类定义(或继承)了自己的成员new和delete函数,则使用那些函数为对象分配和释放内存;否则,调用这些函数的标准库版本。
重载的operator new
和operator delete
成员函数必须是静态的,因为它们要么在构造对象之前使用,要么在撤销对象之后使用,因此,这些函数没有成员函数可操纵。像任意其他静态成员函数一样,new和delete只能直接访问所属类的静态成员。
当operator new
无法满足某一内存分配需求时,以前它会返回一个null指针,新的operator new
会调用一个用户指定的错误处理函数new-handler
,如果new-handler
为null,就会抛出异常。
用户可以通过set_new_handler
来指定自定义的new_handler
。
namespace std{
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
new_handler
是个typedef,定义一个函数指针,该函数没有参数也不返回任何东西。set_new_handler
参数是指定的new_handler
函数,返回值是正在执行(但马上就要被替换)的那个new_handler
函数。throw()表示该函数不抛出任何异常。
可以这样使用set_new_handler
:
void outOfMem()
{
std::cerr << "Unable to satisfy request for memeory\n";
std::abort();
}
int main()
{
std::set_new_handler(outOfMem);
int* pBigDataArray = new int[100000000L];
...
}
如果一定要通过operator new
的返回值判断是否成功分配内存,则可以使用operator new
的nothrow
形式:
Employee* pe = new (std::nothrow) Employee;
if(pe == 0)
{
...
}
看起来似乎可以工作,但是nothrow new
对异常的强制保证性并不高。因为就算operator new
正常分配内存,在Employee的构造函数里面也可能去调用new申请内存,那么这时抛出异常会一如往常地传播。所以,使用nothrow new
只能保证operator new
不抛出异常,不保证像“new (std::nothrow) Employee”
这样的表达式绝不导致异常。
下面给出一个较为符合常规的operator new重载版本伪代码:
void* operator new(std::size_t size) throw(std::bad_alloc)
{
using namespace std;
if (size == 0)
{
size = 1; //处理0-byte申请。将它视为1-byte申请
}
while(true)
{
尝试分配size bytes;
if (分配成功)
return (一个指针,指向分配得来的内存);
//分配失败;找出目前的new-handling函数
new_handler globalHandler = set_new_handler(0);
set_new_handler(globalHandler);
if (globalHandler) (*globalHandler)();
else throw std::bad_alloc();
}
}
将0-byte申请视为1-byte申请是因为,C++规定,即使用户要求0byte,operator new也得返回一个合法指针。
上述while循环退出的唯一办法是:内存被成功分配或new-handler函数让更对内存可用、安装另一个new-handler、卸除new-handler、抛出bad_alloc异常,或是承认失败而直接return。
针对class X设计的operator new,其往往只为大小为sizeof(X)的对象设计。如果被继承,则应改为采用标准operator new:
class Base
{
public:
static void* operator new(std::size_t size) throw(std::bad_alloc);
...
};
class Derived: public:Base //假设Derived未声明operator new
{ ... };
Derived* p = new Derived; //这里调用的是Base::operator new
void* Base::operator new(std::size_t size) throw(std::bad_alloc)
{
if (size != sizeof(Base))
return ::operator new(size);
}
对于operator delete,只需要记住:1.C++保证“删除null指针永远安全”;2.考虑被继承情况。
void Base::operator delete(void* rawMemory, std::size_t size) throw()
{
if (rawMemory == 0) return;
if (size != sizeof(Base))
{
::operator delete(rawMemory);
return;
}
现在,归还rawMemory所指的内存;
return;
}
3. placement new
placement new在已分配的原始内存中初始化一个对象,它与new的其他版本的不同之处在于,它不分配内存。相反,它接受指向已分配但未构造内存的指针,并在该内存中初始化一个对象。
e.g.
Employee* pe1 = new Employee;
Employee* pe2 = new (pe1) Employee;
char buff[sizeof(Employee)];
Employee* pe3 = new (buff) Employee;
pe3->~Employee();
placement new构造对象的地址,可以是在栈上,也可以是在堆上。在栈上使用时,placement new需要手动调用析构函数。
STL中vector就是使用类似placement new的工作原理,先分配一大块内存,然后逐次在预分配内存中下一个可用位置初始化一个对象。
实际上,一般性术语“placement new”是指除了size_t,带任意额外参数的new。如:
class Employee
{
public:
static void* operator new(std::size_t, std::ostream& logStream) throw(std::bad_alloc);
}
//调用operator new并传递cerr为其ostream实参;这个动作会在Employee构造函数抛出异常是泄露内存
Employee* pe = new (std::cerr) Employee;
如果内存分配成功,但是Employee构造函数抛出异常,运行期系统有责任取消operator new的分配并恢复旧观。然而运行期系统无法知道真正被调用的那个operator new如何运作,因此它无法取消分配并恢复旧观,所以上述做法行不通。取而代之的是,运行期系统寻找“参数个数和类型都与operator new相同”的某个operator delete。如果找到,那就是它的调用对象,如果找不到,那就作罢,这就造成内存泄露。
所以这里对应的operator delete应该是:
void operator delete(void*, std::ostream&) throw();
这种接受额外参数的operator delete就称为placement delete。
因此,如果定义了自定义的operator new/placement new成员函数,一定要对应的定义与之匹配的operator delete/placement delete。
由于成员函数的名称会掩盖其外围作用域的相同名称,所以如果自定义了operator/placement new,那么标准库版本的operator new就会被掩盖而无法调用。
class Employee
{
public:
static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc); //这个new会掩盖正常的global形式
...
};
Employee* pb = new Employee; //Error! 正常形式的operator new被掩盖
Employee* pb = new (std::cerr) Employee; //Correct, 调用Employee的placement new
同样,derived class中的operator new也会掩盖global版本和基类的版本:
class Engineer: public Employee
{
public:
static void* operator new(std::size_t size) throw(std::bad_alloc); //重新声明正常形式的new
...
};
Engineer* pe = new (std::clog) Engineer; // Error! Employee的placement new被掩盖了
Engineer* pe = new Engineer; // Correct, 调用Engineer的operator new
缺省情况下,C++在global作用域内提供以下形式的operator new:
void* operator new(std::size_t) throw(std::bad_alloc); // normal new
void* operator new(std::size_t, void*) throw(); // placement new
void* operator new(std::size_t, const std::nothrow_t&) throw(); // nothrow new
如果你自定义了operator new,并且仍然想可以使用global的operator new,可以建立一个base class内含所有正常形式的new和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()
{ ::operator delete(pMemory); }
// placement new/delete
static void* operator new(std::size_t size, void* ptr) throw()
{ 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()
{ return ::operator new(size, nt); }
static void operator delete(void *pMemory, const std::nothrow_t&) throw()
{ ::operator delete(pMemory); }
};
class Employee: 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); // 自定义placement new
static void operator delete(void* pMemory, std::ostream& logStream) throw(); //自定义placement delete
...
};
参考资料:
- 《C++ Primer》
- 《Effective C++》