全面解析C++中的new、operator new与placement new
在C++中,我们通常使用new
来动态分配内存和构造对象。然而,在实际的工程开发中,一个新的问题就会被曝光:new
虽然提供了一个比较便利的方法来分配内存和构造对象,但是也很容易引发一些内存方面的问题。例如:
new
会抛出异常并终止程序,如果内存不足new
会调用构造函数来进行初始化new
分配的内存是连续的,因此不适用于某些特殊情况,例如一个大型的缓存池等
针对上述问题,我们需要使用到new
的两个衍生版本:operator new
和Placement new。下面将会对它们进行详细的讲解。
operator new
operator new
并不是一个关键字,而是一个可重载的操作符。当我们使用new
来动态分配内存时,实际上是调用了全局级别的operator new
。
void* operator new(size_t size);
operator new
的工作方式与C语言中的malloc
函数类似,它会尝试分配指定数量的未初始化的内存,并返回一个指向该内存的指针。但是需要注意的是,operator new
的实现可能会受到编译器、操作系统等诸多因素的影响,因此可能会存在不同的实现方式。
同时,我们也可以通过new (std::nothrow)
来使用operator new
,在分配空间失败时不会抛出异常,而是返回一个nullptr。示例代码如下:
class MyClass {
public:
MyClass(const std::string& str) : m_str(str) { }
~MyClass() { }
private:
std::string m_str;
};
int main() {
MyClass *p = new (std::nothrow) MyClass("Hello, world!");
if (p == nullptr) {
std::cout << "Failed to allocate memory" << std::endl;
}
else {
std::cout << "Successfully allocated memory" << std::endl;
delete p;
}
}
placement new
与operator new
相对应的是placement new
,同样也是一个可重载的操作符。placement new
可以将对象的构造和内存分配操作进行分离,即先分配一块内存,再在该内存上进行对象的构造。以下是placement new
的原型:
void* operator new(size_t size, void* p);
该函数会将一个指向某个内存地址的指针作为参数,并调用该内存地址上的构造函数。示例代码如下:
class MyClass {
public:
MyClass(const std::string& str) : m_str(str) { }
~MyClass() { }
private:
std::string m_str;
};
int main() {
char buffer[sizeof(MyClass)];
MyClass *p = new (buffer) MyClass("Hello, world!");
p->~MyClass();
}
在示例代码中,我们使用了sizeof
计算出MyClass
对象的内存大小,并将该大小作为char
类型数组buffer
的大小分配了相应的内存空间。然后,我们使用placement new
来初始化该空间,并手动调用了析构函数。
示例说明
下面我们将通过一个实际的示例来说明placement new
的使用场景。
在开发某款游戏时,我们需要对游戏中出现的所有对象进行内存池优化,以提高游戏的性能并避免堆内存分配产生的碎片。在这种情况下,我们需要一种灵活、可扩展的内存分配机制,并且需要能够快速地分配相应大小的内存块。
我们可以通过类似于下面的代码来实现一个对象池的内存分配器:
class MemoryPool {
public:
explicit MemoryPool(size_t size);
~MemoryPool();
void* Allocate(size_t size);
void Free(void* p);
private:
char* m_buffer;
};
MemoryPool::MemoryPool(size_t size)
: m_buffer(new char[size])
{
}
MemoryPool::~MemoryPool()
{
delete[] m_buffer;
}
void* MemoryPool::Allocate(size_t size)
{
// 省略池块分配逻辑
return memory;
}
void MemoryPool::Free(void* p)
{
// 省略池块释放逻辑
}
然后,我们需要在该内存池上分配对象时,可以通过以下方式实现:
class MyClass {
public:
MyClass(const std::string& str) : m_str(str) { }
~MyClass() { }
private:
std::string m_str;
};
int main() {
MemoryPool pool(1024);
MyClass *p = new (pool.Allocate(sizeof(MyClass))) MyClass("Hello, world!");
p->~MyClass();
}
在示例代码中,我们首先创建了一个大小为1024字节的内存池pool
,然后通过调用pool.Allocate(sizeof(MyClass))
来分配一个大小为MyClass
对象的内存块,并使用placement new
来构造对象。最后,我们再手动调用对象的析构函数来释放分配的内存。这样就可以实现快速的对象池分配,并且避免了内存分配和析构带来的性能损失。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:全面解析C++中的new,operator new与placement new - Python技术站