C++日积月累—重新认识new/delete

new/delete都做了什么事

下面是一个很简单new/delete的应用,我们对段代码进行反汇编看看,new都做了什么

struct A
{
    A(int a) :a(a) {}
    int a;
};
int main()
{
    A* p = new A{ 1 };
    delete p;
    p = nullptr;
}
image.png

可以看到,new的过程分了两步,一个是用operator new来分配了大小为4的堆空间,然后调用了类A的构造函数。当然这只是分配空间成功的情况,如果分配失败,会发生什么呢?

看看operator new的源码或许对new有个更加深入的认识:

_Ret_notnull_ _Post_writable_byte_size_(_Size)
_VCRT_ALLOCATOR void* __CRTDECL operator new(
    size_t _Size
    );

_Ret_maybenull_ _Success_(return != NULL) _Post_writable_byte_size_(_Size)
_VCRT_ALLOCATOR void* __CRTDECL operator new(
    size_t _Size,
    std::nothrow_t const&
    ) noexcept;

_Ret_notnull_ _Post_writable_byte_size_(_Size)
_VCRT_ALLOCATOR void* __CRTDECL operator new[](
    size_t _Size
    );

_Ret_maybenull_ _Success_(return != NULL) _Post_writable_byte_size_(_Size)
_VCRT_ALLOCATOR void* __CRTDECL operator new[](
    size_t _Size,
    std::nothrow_t const&
    ) noexcept;

到这里我们知道,operator new/operator new []是函数,是函数的话我们就可以尝试继承它。

struct A
{
    A() {};// { std::cout << "constructor" << std::endl; }
    ~A() {};// { std::cout << "destructor" << std::endl; }
    void* operator new [](size_t size)
    {
        std::cout << "operator new size " << size << std::endl;
        return ::operator new(size);
    }

    void operator delete [](void* point)
    {
        std::cout << "operator delete" << std::endl;
        ::operator delete(point);
    }
private:
    int a;
};

int main()
{
    A* p = nullptr;
    try
    {
        //这里new一个适合大小的
        p = new A[(std::numeric_limits::max() >> 1) / sizeof(A)];
    }
    catch (std::bad_alloc& e)
    {
        std::cout << e.what() << std::endl;
    }
    if (p)
    {
        delete[] p;
        p = nullptr;
    }   
}

默认情况下会抛出异常,并且能被std::bad_alloc捕获。当然这只是默认情况,我们是可以对分配失败进行一些特殊处理。

通过源码我们发现operator new/operator new []重载了带有std::nothrow_t const&的接口。函数还修饰成了noexcept,这表明了不会再往外抛异常。并且查看std::nothrow_t源码发现,声明了全局的std::nothrow供我们使用。所以我们就有新的用法。把上面例子的new[]改成 new(std::nothrow)[],类A重载带std::nothrow_t const&参数的接口。可以分别实施带noexcept和不带noexcept的情况。

    void* operator new [](size_t size, std::nothrow_t const&) noexcept
    {
        std::cout << "operator new size" << size << "with std::nothrow" << std::endl;
        return ::operator new(size);
    }

    void operator delete [](void* point, std::nothrow_t const&)
    {
        std::cout << "operator delete with std::nothrow" << std::endl;
        ::operator delete(point);
    }
        // ...
    p = new(std::nothrow) A[(std::numeric_limits::max() >> 1) / sizeof(A)];

默认抛出bad_alloc异常,如果有noexcept且有nothrow返回NULL,如果有noexcept没有nothrow则直接dump。

new/delete与operator new/operator delete的区别

new

  • 是操作符,所以不可能被重载
  • 调用operator new分配空间,并且调用匹配的构造函数。如果operator new分配空间分配空间失败,则不会再调用构造函数。

operator new

  • 可以被重载,但是要满足这些条件:返回类型必须声明为void*;第一个参数类型必须为表示要求分配空间的字节大小,类型为size_t(实际大小跟编译器有关)。
  • 重载时可以带其他的参数。
  • 如果分配空间失败则:
    如果有new_handler,则调用new_handler,否则
    默认抛出bad_alloc异常,如果有noexcept且有nothrow返回NULL,如果有noexcept没有nothrow则直接dump。

delete 与 delete operator类似
operator new与operator delete和C语言中的malloc与free对应,只负责分配及释放空间。但使用operator new分配的空间必须使用operator delete来释放,而不能使用free,因为它们对内存使用的登记方式不同。反过来亦是一样。

你可能感兴趣的:(C++日积月累—重新认识new/delete)