C++ - 深入理解new

C++中new有三种形式:new operator、operator new和placement new。

1. new operator

new operator就是我们平时使用的new表达式,来为特定类型分配内存,并在新分配的内存中构造该类型的一个对象。e.g.

Employee* sp = new Employee("001");

它实际上发生了三个步骤:

  1. 该表达式调用名为operator new的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;
  2. 运行该类型的一个构造函数,用指定初始化式构造对象;
  3. 返回指向新分配并构造的对象的指针。

伪代码就是:

4. Employee* sp = (Employee*)malloc(sizeof(Employee));
5. sp->Employee::Employee("001");
6. return sp;

当使用delete表达式delete sp;删除动态分配对象的时候,发生两个步骤:

  1. 对指向对象运行适当的析构函数;
  2. 通过调用名为operator delete的标准库函数释放该对象所用内存。

调用operator delete函数不会运行析构函数,它只释放指定的内存。

new和delete表达式的行为是固定的,不能重定义的。

2. operator new

默认情况下,new表达式通过调用由标准库定义的operator new版本分配内存。我们也可以通过定义自定义类型的名为operator newoperator delete的成员函数,管理用于自身类型的内存。

编译器看到类类型的new或delete表达式的时候,它查看该类是否有operator newoperator delete成员,如果类定义(或继承)了自己的成员new和delete函数,则使用那些函数为对象分配和释放内存;否则,调用这些函数的标准库版本。

重载的operator newoperator 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 newnothrow形式:

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
	...
};


参考资料:
  1. 《C++ Primer》
  2. 《Effective C++》
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值