C++中的new和delete

1. operator new 、placement new 和 operator delete

我们首先来看一下下面的简单示例

#include <iostream>

class MTestClass
{
public:
    MTestClass(int num) : m_Number(num) {
        std::cout << "执行构造函数 MTestClass(int num)" << std::endl;
    }
    ~MTestClass() {
        std::cout << "执行析构函数" << std::endl;
    }

    void print(void) {
        std::cout << "Number is : " << m_Number << std::endl;
    }

private:
    int m_Number = 10;
};

int main(int argc, char** argv)
{
    MTestClass *p = new MTestClass(50);
    p->print();
    delete p;

    system("pause");
    return 0;
}

上面的代码很简单,
在代码中我们 new 了一个对象 p
然后调用函数 p->print()
最后将对象删除, delete p
上面的程序运行结果是这样的:

执行构造函数 MTestClass(int num)
Number is : 50
执行析构函数

我们可以对这个过程做出如下猜测的操作步骤:

  1. 在堆中使用 malloc 函数创建 sizeof(MTestClass) 大小的内存,并将内存转为 MTestClass* 类型;(使用 operator new
  2. 调用构造函数 MTestClass(50) 并将已分配的内存设置值。(使用placement new)
  3. 调用函数 p->print();
  4. 调用析构函数 ~MTestClass()
  5. 释放申请的堆空间内存。(operator delete)
  • operator new : 是指形如以下形式 void* operator new (size_t size) 形式的函数,分配 size 大小的内存空间。
  • placement new : 调用构造函数,并为已将分配的内存空间赋值,这块内存可以是栈内存也可以是堆内存。
    形如 ::new(obj) MTestClass(50),为指针 obj 指向的已存在的内存空间赋值
  • operator delete : 释放堆内存空间。

我们改写上面的示例:

#include <iostream>

class MTestClass
{
public:
    MTestClass(int num) : m_Number(num) {
        std::cout << "执行构造函数 MTestClass(int num)" << std::endl;
    }
    ~MTestClass() {
        std::cout << "执行析构函数" << std::endl;
    }

    void* operator new(size_t size) {
        std::cout << "执行 operator new !" << std::endl;
        return ::operator new(size);
    }

    void operator delete(void* pWhere) {
        std::cout << "执行 operator delete !" << std::endl;
        return ::operator delete(pWhere);
    }

    void print(void) {
        std::cout << "Number is : " << m_Number << std::endl;
    }

private:
    int m_Number = 10;
};

int main(int argc, char** argv)
{
    // operator new 
    MTestClass* p = (MTestClass*)MTestClass::operator new(sizeof(MTestClass));
    p->print();

    // placement new 
    ::new(p) MTestClass(50);
    p->print();

    // 执行析构函数
    p->~MTestClass();

    MTestClass::operator delete(p);

    system("pause");
    return 0;
}

程序的运行结果如下:
执行 operator new !
Number is : -842150451
执行构造函数 MTestClass(int num)
Number is : 50
执行析构函数
执行 operator delete !

根据运行结果可以看出,

  1. 首先执行 operator newoperator new 虽然分配了内存空间,但是并没有为这块内存空间赋值;
  2. 执行 placement new,调用构造函数并为这块空间赋值。
  3. ::new(p) MTestClass(50);
  4. 执行析构函数
  5. 执行 operator delete 释放内存。

2. new[] 和delete[]

我们经常说 new[]delete[] 一定要成对出现,那么为什么呢?
其实是这样的,对于普通数据类型使用 new[] 创建的数组,使用 delete[] 和直接使用 delete 释放内存效果是一样的,对于自定义的类型,如果显示的声明了析构函数,则需要使用 delete[], 如果不是显示的声明析构函数,那么 deletedelete[] 的效果是相同的。 如果显示声明了析构函数,delete[] 需要调用n次析构函数,那么 delete[] 是怎么知道申请了多少个内存空间呢?我们可以看一下下面的伪代码

MTestClass *p = new MTestClass[10];
delete[] p;

分析如下:

首先 new MTestClass[10] 会申请 10 * sizeof(MTestClass) + sizeof(size_t) 个内存空间,为什么是 10 * sizeof(MTestClass) + sizeof(size_t) 个而不是 10 * sizeof(MTestClass) 个,是因为他要用 sizeof(size_t) 个字节存储数组的元素个数。 在调用 delete[] 的时候会根据第一个字节的个数去调用n次析构函数,然后释放掉整个内存空间。

为了验证上面的说法,我们将代码改写如下:

#include <iostream>

class MTestClass
{
public:
    MTestClass(){
        std::cout << "执行构造函数 MTestClass()" << std::endl;
    }
    MTestClass(int num) : m_Number(num) {
        std::cout << "执行构造函数 MTestClass(int num)" << std::endl;
    }
    ~MTestClass() {
        std::cout << "执行析构函数" << std::endl;
    }

    void* operator new(size_t size) {
        std::cout << "执行 operator new !" << std::endl;
        return ::operator new(size);
    }

    void operator delete(void* pWhere) {
        std::cout << "执行 operator delete !" << std::endl;
        return ::operator delete(pWhere);
    }

    void* operator new[](size_t size) {
        std::cout << "执行 operator new[] !" << "\tsize is " << size << std::endl;
        return ::operator new[](size);
    }

    void operator delete[](void* pWhere){
        std::cout << "执行 operator delete[] !" << std::endl;
        return ::operator delete[](pWhere);
    }

    void print(void) {
        std::cout << "Number is : " << m_Number << std::endl;
    }

private:
    int m_Number = 10;
    int m_Number2 = 20;
};

int main(int argc, char** argv)
{
    std::cout << sizeof(MTestClass) << ", " << sizeof(size_t) << std::endl;
    MTestClass* p = new MTestClass[5];

    delete[] p;

    system("pause");
    return 0;
}

运行结果为:
8, 4
执行 operator new[] ! size is 44
执行构造函数 MTestClass()
执行构造函数 MTestClass()
执行构造函数 MTestClass()
执行构造函数 MTestClass()
执行构造函数 MTestClass()
执行析构函数
执行析构函数
执行析构函数
执行析构函数
执行析构函数
执行 operator delete[] !

可以看出,operator new[] 申请的内存空间大小为44个字节,sizeof(MTestClass) 为8字节,而 sizeof(size_t) 为4字节,正好为 44 = 5 * 8 + 4,验证了申请空间大小为 n * sizeof(MTestClass) + sizeof(size_t)

如果我们不显示声明析构函数,又会怎么样呢??

去掉析构函数的完整代码如下:

#include <iostream>

class MTestClass
{
public:
    MTestClass(){
        std::cout << "执行构造函数 MTestClass()" << std::endl;
    }
    MTestClass(int num) : m_Number(num) {
        std::cout << "执行构造函数 MTestClass(int num)" << std::endl;
    }

    void* operator new(size_t size) {
        std::cout << "执行 operator new !" << std::endl;
        return ::operator new(size);
    }

    void operator delete(void* pWhere) {
        std::cout << "执行 operator delete !" << std::endl;
        return ::operator delete(pWhere);
    }

    void* operator new[](size_t size) {
        std::cout << "执行 operator new[] !" << "\tsize is " << size << std::endl;
        return ::operator new[](size);
    }

    void operator delete[](void* pWhere){
        std::cout << "执行 operator delete[] !" << std::endl;
        return ::operator delete[](pWhere);
    }

    void print(void) {
        std::cout << "Number is : " << m_Number << std::endl;
    }

private:
    int m_Number = 10;
    int m_Number2 = 20;
};

int main(int argc, char** argv)
{
    std::cout << sizeof(MTestClass) << ", " << sizeof(size_t) << std::endl;
    MTestClass* p = new MTestClass[5];

    delete[] p;

    system("pause");
    return 0;
}

程序运行结果:
8, 4
执行 operator new[] ! size is 40
执行构造函数 MTestClass()
执行构造函数 MTestClass()
执行构造函数 MTestClass()
执行构造函数 MTestClass()
执行构造函数 MTestClass()
执行 operator delete[] !

从结果可以看出,申请的大小为40,验证了如果没有显示的声明析构函数,则会申请 sizeof(MTestClass) * n 个字节的内存空间,这时使用delete 和使用 delete[] 效果是相同的。

通过上面这两个例子也可以看出
operator newoperator new[] 的效果是相同的,都是申请 size 大小的空间;
operator deleteoperator delete[] 的效果也是相同的,都是释放堆内存空间。

不会飞的纸飞机
扫一扫二维码,了解我的更多动态。

下一篇文章:内存池的简单实现