reference

new and malloc

new 和 delete

先看看下面的例子:

#include <iostream>
#include <malloc.h>

using namespace std;

class T
{
public:    
    T(){v = 666;}
    ~T(){cout << "delete T\n";}
    void show(){cout << v << endl;}
private:
    int v;
};

int main()
{
    cout << "new:\n";
    T* t1 = new T;
    t1->show();
    delete t1;

    cout << "malloc:\n";
    T* t2= (T*)malloc(sizeof(T));
    if(t2 != nullptr)
    {
        t2->show();
        free(t2);
    }
    return 0;
}

输出如下:

new:
666
delete T
malloc:
16325248
  • new 会调用对象构造函数,在堆区新建一个对象,并返回该对象的指针(失败抛出 bad_alloc 异常)。
  • malloc 在堆区分配一块内存,用mallco在堆区创建一个对象是不会调用构造函数的(失败返回 nullptr)。
  • delete 会释放堆区内存,并调用对应的析构函数。
  • free 只会释放内存。

new 创建对象

  1. 调用用operatoe new()为对象分配内存(常用malloc)
  2. 调用构造函数来初始化内存。

delete 销毁对象

  1. 调用析构函数。
  2. 调用operator delete() 释放内存(常用free)。

构造和析构函数的调用是由编译器控制的,可以重载 operator new()和operator delete()。

why new delete

  1. 在OOP中,需要在创建对象同时要自动执行构造函数,在对象销毁之前需要自动执行析构函数。
  2. malloc、free是C、C++标准库函数,编译器无法控制其行为,不能让其在执行时强制执行构造和析构函数。
  3. new、delete 是C++运算符,由编译器控制其行为,可以让其在执行时自动执行构造和析构函数。

operator new()

  • param1:size_t
  • ......(placement new)
  • return:void*

operator new重载可以放在全局或类内部。

当编译器发现有new关键字,会在当前类和基类中寻找operator new,找不到就在全局中找,再找不到就用默认的。

类中的operator new默认static

class T
{
public:    
    T(){v = 666;}
    ~T(){cout << "delete T\n";}
    void show(){cout << v << endl;}

    void* operator new(size_t size)
    {
        cout << "operator new(std::size_t size)\n";
        return malloc(size);
    }

private:
    int v;
};

int main()
{
    cout << "new:\n";
    T* t1 = new T;
    t1->show();
    delete t1;
    return 0;
}

输出:

new:
operator new(std::size_t size)
666
delete T

placement newoperator new一种重载形式

class T
{
public:    
    T(int _v):v(_v){}
    ~T(){cout << "delete T\n";}
    void show(){cout << v << endl;}

    void* operator new(size_t size)
    {
        cout << "operator new(std::size_t size)\n";
        return malloc(size);
    }

    void* operator new(size_t size, void* ptr)
    {
        cout << "operator new(size_t size, void* ptr)\n";
        return ptr;
    }

private:
    int v;
};
int main()
{
    cout << "new:\n";
    T* t1 = new T(1);
    t1->show();
    T* t2 = new(t1) T(2);
    t1->show();
    t2->show();
    printf("%x\n", t1);
    printf("%x\n", t2);
    //delete t2;
    delete t1;
    return 0;
}

输出:

new:
operator new(std::size_t size)
1
operator new(size_t size, void* ptr)
2
2
7e2a90
7e2a90
delete T

可见这里的t1 和 t2都指向同一个地址,因此delete一个就行。

  • placement new不需要申请新内存和释放旧内存,可以用在内存池里面。
  • operator new可以有其它参数,这里不过多赘述。
  • operator delete重载和operator new相同,但一般不会重载,因为重载版本delete不可手动调用。

new[] 和 delete[]

这两个是针对数组的版本,看下面的例子:

class A
{
public:
    A() { cout << "create A\n"; };
    ~A() { cout << "delete A\n"; };
    unsigned char ch{ 'a' };
};

int main()
{
    constexpr const size_t N = 3;
    A* p = new A[N];
    cout << *((size_t*)p - 1) << endl;
    delete[] p;
    return 0;
}

输出:

create A
create A
create A
3
delete A
delete A
delete A

可见 new[] 会对每一个对象调用一次构造函数,delete[] 会对每一个对象调用一次析构函数。

C++ 标准里面的相关定义如下:

void *operator new(size_t);    
void *operator delete(void *);   
void *operator new[](size_t);    
void *operator delete[](void *);   

并且部分实现如下:(仍然调用了operator new())

void* operator new[] (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
{
    /*......*/
	return ::operator new(sz);
}
  • 内建数据类型:new[] 基本与 new 等价,delete[] 与 delete 也类似。

  • 构造函数不带参数:编译器会在返回指针前面(低地址处)多分配 sizeof(size_t) 的空间用于储存对象个数。

    这里要求该类要显示的实现析构函数才能拿到对应的数组长度(MSVC)。

    // ~A() { cout << "delete A\n"; };
    // N == *((size_t*)p - 1)

编译器默认函数

C++ 编译器默认在类里面会生成下面的函数:

A();                         //默认构造
~A();                        //默认析构
A(const A&);                 //默认(浅)拷贝
A& operator = (const A& a);  //默认赋值

C++ 中不允许定义值传递的拷贝构造函数(如下):

A(A a){} 					// ERROR

原因

值传递的拷贝构造函数在传参过程中要调用拷贝构造函数本身,在调用拷贝构造函数时要先进行参数传递,参数传递又要调用拷贝构造函数,会不停递归分配堆栈。

赋值函数是把一个对象赋值给另一个已存在的对象,使得该对象和原对象属性一致。

不想编译器默认生成上面的函数,可以作为 private 或在后面加上 =delete。