C++中的new和delete及其重载


title: new & delete
date: 2024-03-09 19:00:02
categories:

  • C++
  • 其他
    tags: #vs那些事

new & delete

newdelete是用于动态内存管理的两个重要运算符。它们分别用于在堆上分配和释放内存,同时也负责调用对象的构造函数和析构函数。

  • new用于分配内存并调用构造函数初始化对象。
  • delete用于释放由new分配的内存,并调用析构函数清理资源。
  • 使用new分配的每块内存都应该用对应的delete释放,以避免内存泄漏

new操作符是C++语言内置的,用于分配内存并调用构造函数来创建对象。当你使用new时,它会执行两个主要步骤:

  1. 调用operator new函数来分配足够的内存。
  2. 在分配的内存上调用对象的构造函数来初始化对象。

例如,以下代码使用了new操作符来创建一个string对象:

std::string* ps = new std::string("Hello, World!");

区别于C中的malloc和free mallocfree是C语言中用于动态内存分配的函数,

malloc和free

  • malloc用于分配一定大小的内存块,并返回一个指向该内存的指针。它不会调用构造函数,所以不会初始化对象。
  • free用于释放malloc分配的内存。它不会调用析构函数,所以不会清理对象。
  • malloc的返回类型是void*,所以在使用前需要强制转换到适当的类型。

new和delete

  • new用于分配内存并自动调用构造函数来初始化对象。
  • delete用于释放new分配的内存,并自动调用析构函数来清理对象。
  • new返回的是正确的类型指针,不需要类型转换。

区别

  1. 类型安全new是类型安全的,会返回正确的类型指针;而malloc返回void*,需要强制类型转换。
  2. 初始化new会自动调用构造函数初始化对象;malloc仅分配内存,不初始化。
  3. 异常处理new在内存分配失败时会抛出异常;malloc在失败时返回NULL
  4. 配对使用:使用new分配的内存必须用delete释放;使用malloc分配的内存必须用free释放。
  5. 重载newdelete可以被重载;mallocfree不能。
// 使用malloc分配内存
int* myArray = (int*)malloc(10 * sizeof(int));

// 使用free释放内存
free(myArray);

operator new&operator delete

先说operator new和new的区别,newdelete是操作符,它们不仅负责内存的分配和释放,还会调用对象的构造函数和析构函数。而operator newoperator delete是全局函数,它们只负责内存的分配和释放,不涉及任何构造或析构过程。简单来说,当你创建和删除一个对象时:

  • 使用new操作符,它会:

    1. 调用operator new来分配内存。
    2. 在分配的内存上调用构造函数来初始化对象。
    3. 返回一个指向新对象的指针。
  • 使用delete操作符,它会:

    1. 调用对象的析构函数。

    2. 调用operator delete来释放内存。

因此,newdelete操作符与operator newoperator delete函数的主要区别在于对象的生命周期管理。newdelete操作符会管理对象的创建和销毁,而operator newoperator delete函数只管理内存。123

这也是为什么你可以重载operator newoperator delete来自定义内存分配和释放的行为,但不能重载newdelete操作符本身。它们的行为是固定的,总是包括调用构造函数和析构函数。

**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 newoperator delete是特殊的情况,它们是与内存分配和释放相关的函数,而不是传统意义上的运算符。

operator newoperator delete的命名方式可能会让人误以为它们是重载了newdelete运算符,但实际上它们是独立的函数。你可以重载这些函数来改变默认的内存分配和释放行为,但这并不影响newdelete运算符本身的语义。

因此,当我们说"重载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机制 - 知乎 (zhihu.com)

显式调用析构

显式调用析构函数是指直接使用对象的析构函数名来销毁对象,而不是等待对象的作用域结束时自动调用析构函数。这通常用于与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 来释放内存块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值