title: new & delete
date: 2024-03-09 19:00:02
categories:
- C++
- 其他
tags: #vs那些事
new & delete
new
和delete
是用于动态内存管理的两个重要运算符。它们分别用于在堆上分配和释放内存,同时也负责调用对象的构造函数和析构函数。
new
用于分配内存并调用构造函数初始化对象。delete
用于释放由new
分配的内存,并调用析构函数清理资源。- 使用
new
分配的每块内存都应该用对应的delete
释放,以避免内存泄漏
new操作符是C++语言内置的,用于分配内存并调用构造函数来创建对象。当你使用new
时,它会执行两个主要步骤:
- 调用
operator new
函数来分配足够的内存。 - 在分配的内存上调用对象的构造函数来初始化对象。
例如,以下代码使用了new
操作符来创建一个string
对象:
std::string* ps = new std::string("Hello, World!");
区别于C中的malloc和free malloc
和free
是C语言中用于动态内存分配的函数,
malloc和free:
malloc
用于分配一定大小的内存块,并返回一个指向该内存的指针。它不会调用构造函数,所以不会初始化对象。free
用于释放malloc
分配的内存。它不会调用析构函数,所以不会清理对象。malloc
的返回类型是void*
,所以在使用前需要强制转换到适当的类型。
new和delete:
new
用于分配内存并自动调用构造函数来初始化对象。delete
用于释放new
分配的内存,并自动调用析构函数来清理对象。new
返回的是正确的类型指针,不需要类型转换。
区别:
- 类型安全:
new
是类型安全的,会返回正确的类型指针;而malloc
返回void*
,需要强制类型转换。 - 初始化:
new
会自动调用构造函数初始化对象;malloc
仅分配内存,不初始化。 - 异常处理:
new
在内存分配失败时会抛出异常;malloc
在失败时返回NULL
。 - 配对使用:使用
new
分配的内存必须用delete
释放;使用malloc
分配的内存必须用free
释放。 - 重载:
new
和delete
可以被重载;malloc
和free
不能。
// 使用malloc分配内存
int* myArray = (int*)malloc(10 * sizeof(int));
// 使用free释放内存
free(myArray);
operator new&operator delete
先说operator new和new的区别,new
和delete
是操作符,它们不仅负责内存的分配和释放,还会调用对象的构造函数和析构函数。而operator new
和operator delete
是全局函数,它们只负责内存的分配和释放,不涉及任何构造或析构过程。简单来说,当你创建和删除一个对象时:
-
使用new操作符,它会:
- 调用
operator new
来分配内存。 - 在分配的内存上调用构造函数来初始化对象。
- 返回一个指向新对象的指针。
- 调用
-
使用delete操作符,它会:
-
调用对象的析构函数。
-
调用
operator delete
来释放内存。
-
因此,new
和delete
操作符与operator new
和operator delete
函数的主要区别在于对象的生命周期管理。new
和delete
操作符会管理对象的创建和销毁,而operator new
和operator delete
函数只管理内存。123
**operator new
是一个用于内存分配的函数。**当你使用new
关键字创建对象时,new
操作符会调用operator new
函数来分配足够的内存。这个函数通常返回一个指向未初始化内存的void*
指针。operator new
可以被重载,这意味着你可以自定义内存分配的行为。
所以说功能上来说 new = operator new+构造函数
例如,你可以这样调用operator new
:
void *rawMemory = operator new(sizeof(MyClass));
这会分配足够的内存来存储MyClass
类型的对象。然后,new
操作符会在这块分配的内存上调用构造函数来初始化对象。
::operator new是全局的operator new如果没有重载版本,那么默认就是全局版本
刚开始理解不了,operator本身就是重载的意思,类似于operator+,难道operator new不是重载new吗,如果说operator new可以重载不应该是operator operator new这样才对
解释:当我们说"重载运算符"时,通常是指像
operator+
或operator==
这样的运算符。然而,operator new
和operator delete
是特殊的情况,它们是与内存分配和释放相关的函数,而不是传统意义上的运算符。
operator new
和operator delete
的命名方式可能会让人误以为它们是重载了new
和delete
运算符,但实际上它们是独立的函数。你可以重载这些函数来改变默认的内存分配和释放行为,但这并不影响new
和delete
运算符本身的语义。因此,当我们说"重载
operator new
"时,我们是指提供一个新的函数定义来替代默认的内存分配行为。这不同于重载如operator+
这样的运算符,后者涉及到改变已有运算符的行为。
此外,还有一种特殊的operator new
称为placement new,它允许在已经分配的内存上构造对象。
placement new
placement new
是C++中的一个特殊功能,它允许你在已经分配的内存上构造一个对象。这与普通的new
操作符不同,后者会自动分配内存然后在该内存上构造对象。placement new
的用途通常是为了在特定的内存位置上创建对象,这可以用于优化性能,避免不必要的内存分配和释放,或者用于在特定的硬件地址上构造对象。
使用placement new
时,你需要提供一个指向已分配内存的指针。这里是一个简单的例子:
#include <new> // 必须包含这个头文件 char buffer[sizeof(MyClass)]; // 分配一块足够大的内存 MyClass *pMyClass = new (buffer) MyClass; // 在buffer指向的内存上构造对象
在这个例子中,buffer
是一个字符数组,我们在它的地址上使用placement new
构造了一个MyClass
类型的对象。注意,因为placement new
不分配内存,所以你必须确保提供的内存足够大且适当对齐,以容纳你想要构造的对象类型。
重要的是要记住,使用placement new
创建的对象不应该使用普通的delete
来销毁,因为这会尝试释放你提供的内存,而这块内存可能不是通过普通的new
分配的。相反,你应该显式地调用对象的析构函数来销毁它:
pMyClass->~MyClass(); // 显式调用析构函数
这样做之后,你可以自由地重新使用或释放那块内存,但这个管理过程是由程序员负责的。
显式调用析构
显式调用析构函数是指直接使用对象的析构函数名来销毁对象,而不是等待对象的作用域结束时自动调用析构函数。这通常用于与placement new一起,手动管理对象的生命周期。
显式调用析构函数的语法如下:
obj->~MyClass(); // 对于动态分配的对象
myObject.~MyClass(); // 对于栈分配的对象
在这里,MyClass
是类名,~MyClass()
是析构函数。通过这种方式,可以在对象的生命周期内的任何时刻销毁对象。
需要注意的是,显式调用析构函数通常只在特殊情况下使用,例如在使用placement new时,或者在使用自定义内存管理策略时。在大多数情况下,应该避免显式调用析构函数,因为它可能导致对象被销毁两次,从而引发未定义行为。
如果使用placement new在预分配的内存上构造了一个对象,你需要在不再需要该对象时显式调用其析构函数来销毁它,然后释放内存。这是因为placement new不会在对象的作用域结束时自动调用析构函数。
如果使用placement new
和显式调用析构函数的组合来管理对象的生命周期,那么当对象的作用域结束时,编译器不会再次自动调用析构函数。这是因为编译器只会自动调用那些以普通方式(非placement new)构造的对象的析构函数。
当手动调用析构函数后,对象已经被销毁,所以编译器不会再尝试销毁它。因此,不会出现二次销毁的情况。但是需要确保在调用析构函数之后,也使用正确的方法释放内存(例如,如果使用operator new
分配内存,则应使用operator delete
释放内存)。
这种手动管理对象生命周期的方法需要谨慎使用,因为如果忘记调用析构函数或释放内存,就可能导致资源泄漏或其他未定义行为。所以,这通常只在特殊情况下使用,比如在自定义内存管理或优化性能时。
新问题:
template<typename T>
class Vector {
// ... 其他成员 ...
void popback() {
if (m_Size > 0) {
m_Size--;
m_Data[m_Size].~T(); // 显式调用析构函数
}
}
void clear() {
for (size_t i = 0; i < m_Size; i++) {
m_Data[i].~T(); // 显式调用析构函数
}
m_Size = 0;
}
// ... 其他成员 ...
};
这里的.~T();
~T()
表示调用类型T
的析构函数。这是泛型编程的一部分,允许Vector
类处理任何类型的对象。当T
是一个类类型时,这个调用将执行该类的析构函数,负责清理资源。当T
是一个内置类型时(int,float,string…),这个调用不会有任何效果,但仍然是合法的。
也就是说,如果此处的T是另一个类类型例如
struct Vector3
{
float x = 0.0f, y = 0.0f, z = 0.0f;
Vector3();
~Vector3();
}
那么.~T()就相当于是调用Vector3来销毁对象,之后还需要operator delete 来释放内存块。