运算符重载之new与delete

关于new/delete,援引C++ Primer中的一段话:

  某些应用程序对内存分配有特殊的要求,因此我们无法直接将标准的内存管理机制直接应用于这些程序。他们常常需要自定义内存分配的细节,比如使用关键字new将对象放置在特定的内存空间中。为了实现这一目的,应用程序需要重载new运算符和delete预算符以实现内存分配的过程。在讲new/delete系列的重载之前,我们先要明确堆对象构造与析构的过程。

  讲真,对于new/delete的重载,在之前一直不觉得其有什么实质性的作用,直到我加入了一现在的一家公司做数据通信才碰到。

 

new 或者 new[ ]的作用:

当我们使用一条new表达式时:

string *pstr     = new string("test");
string *pstrArr = new string[10];

实际执行了三部操作:

1)new表达式调用一个名为operator new(或者operator new[ ])的标准库函数。该函数的作用是分配一块足够大的内存空间以便存储特定类型的对象。

2)编译器运行相应的构造函数初始化这块内存。

3)对象被分配空间完成,返回一个带类型的指针指向这块个象。

 

delete delete[ ]的作用

delete pstr;
delete [] pstrArr;

1)调用指针所指对象执行析构函数。

2)编译器调用operator delete(或者operator delete[ ])的标准函数释放内存。

 

new/delete默认重载

C++默认的几种重载模型如下

//标准版本(常用)
void
*operator new(size_t,nothrow &); void *operator new[](size_t,nothrow &); void *operator delete(void *,nothrow &) noexcept; void *operator delete(void *,nothrow &) noexcept;
//不会抛出异常的版本
void *operator new(size_t,nothrow &) noexcept; void *operator new[](size_t,nothrow &) noexcept; void *operator delete(void *,nothrow &) noexcept; void *operator delete(void *,nothrow &) noexcept;

 

 

#include <iostream>
#include <new>


using namespace std;


int main(int argc, char *argv[])
{
    double * ptr[50];
    for(int i=0; i<50; i++)
    {
        ptr[i] = new double[50000000];
        if(ptr[i] == NULL)
        {
            cout<<"new error"<<endl;
            exit(1);
        }

        cout<<"ptr["<<i<<"]"<<"->50000000 duble"<<endl;
    }
    return 0;
}

 

 运行结果:

 

 

 

#include <iostream>
#include <new>


using namespace std;


int main(int argc, char *argv[])
{
    double * ptr[50];
    for(int i=0; i<50; i++)
    {
        ptr[i] = new (nothrow)double[50000000];
        if(ptr[i] == NULL)
        {
            cout<<"new error"<<endl;
            exit(1);
        }

        cout<<"ptr["<<i<<"]"<<"->50000000 duble"<<endl;
    }
    return 0;
}

 

运行结果:

 

 

模仿默认实现重载

以上函数既可以全局重载,也可以成员重载,全局重载不常用,下面是成员重载的方式,全局的重载只需要将他们拷贝到类外就好了。

#include <iostream>

using namespace std;

class A
{
    public:
    A(){
        cout<<"A constructor"<<endl;
    }
    ~A(){
        cout<<"A destructor"<<endl;
    }
    
    void * operator new (size_t size)
    {
        cout<<"new "<<size<<endl;
        return malloc(size);
    }
    
    void operator delete(void *p)
    {
        cout<<"delete"<<endl;
        free(p);
    }
    
    void * operator new[] (size_t size) noexcept
    {
        cout<<"new[] "<<size<<endl;
        return malloc(size);
    }
    
    void operator delete[](void *p) noexcept
    {
        cout<<"delete[] "<<endl;
        free(p);
    }
    
    void dis()
    {
        cout<<"dis"<<endl;
    }

    private:
    int a;
    double d;
};



using Func = void (*)();

int main(int argc, char *argv[])
{
    cout<<"sizeof(A):"<<sizeof(A)<<endl;

    cout<<"\n---new A---"<<endl;
    A * newA = new A;

    cout<<"---new A[1]---"<<endl;
    A * newA1 = new A[1];

    cout<<"\n---new A[2]---"<<endl;
    A * newA2 = new A[2];


    cout<<"\n---delete A---"<<endl;
    delete newA;

    cout<<"\n---delete A[1]---"<<endl;
    delete newA1;

    cout<<"\n---delete A[2]--"<<endl;
    delete [] newA2;

    return 0;
}

 

运行结果:

 

new[ ]分配内存大小与期望不一致的问题

调用new[]函数的时候,其实际生成的内存大小与对象个数并不成比例,

比如A *p = new A[10];

实际上生成的内存大小并非sizeof(A) * 10,还要生成一个内存大小计数

这个要归咎于自定义对象是否自定义了析构函数,如果没有自定义析构函数,那么编译器会优化内存申请,不会多出四个字节。

详情请查看这篇博客:

https://www.cnblogs.com/tp-16b/p/8684298.html

 

关于C++内存操作符重载的特别声明

1)   隐式静态,成员重载不可在函数操作数据成员变量(加 static修饰也不会有问题)。

2)operator new/operator new[ ]第一个参数必须是size_t类型,且不能含有默认实参。

3)operator new/operator new[ ]重载时,可以提供额外的参数,使用时必须使用new的定位形式将实参传给定位的形参。

4)void * new(size_t , void *)这个函数只有系统提供的版本,不可重载。

5)operator delete/operator delete[ ]第一个形参必须是void *,该函数还可以包含一个size_t的形参。

 

new[1] 与new 的不同

 

operator new /new[ ]  /// operator delete /delete[ ]与其他运算符重载的异同

operator new 和 operator delete实际上是和其他的operator函数(如operator =)是不一样的,这两个函数并没有重载new 或者 delete表达式,实际上我们根本无法自定义表new或者delete达式的行为。

我们提供的operator new 函数和 operator delete函数覅热目的在于改变内存的分配方式,当不管怎么样不能改变new运算符和delete运算符的基本含义。

  

 

占位符问题:

既然说到了new/delete重载,那么我们就说一下占位符的问题。

空的结构体/空类默认有一个占位符。所以默认大小是一个字节。

例如:

#include <iostream>


using namespace std;

class EmptyClass
{};

struct EmptyStruct
{};

class EmptyClassWiFlag
{
    char ch[0];
};

struct EmptyStructWithFlg
{
    char ch[0];
};

int main(int argc, char *argv[])
{
    cout<<"sizeofClass:"<<sizeof(EmptyClass)<<endl;
    cout<<"sizeofClass:"<<sizeof(EmptyStruct)<<endl;
    cout<<"sizeofClass:"<<sizeof(EmptyClassWiFlag)<<endl;
    cout<<"sizeofClass:"<<sizeof(EmptyStructWithFlg)<<endl;
    return 0;
}

 那么当我们在一个空类中声明一个类型的数组,并且数组元素个数设置为0时他就真的成了一个空类。

这种真正的空类有什么用呢?

如上我们有时会重载operator new / operator new [ ] ,有时我们会根据项目需求申请超出类大小的内存。那么如何拿到这个类对象还没用到的内存就是一个问题。char [0]这时就派上用场了。

这里重载一个new的定位形式placer new,来展示空占位符的作用:

#include <iostream>
#include <string.h>


using namespace std;


class EmptyClassWiFlag
{
public:
    EmptyClassWiFlag()
        :a(0)
    {}
    ~EmptyClassWiFlag()
    {}
    void* freeMemBegain()
    {
        return ch;
    }
private:
    int a;
    char ch[0];
};

void* operator new(size_t memSize,size_t reqMemSize)
{
    cout<<"memsize:"<<memSize<<endl;
    cout<<"reqMemSize:"<<reqMemSize<<endl;
    return malloc(reqMemSize);
}

#define MEMCOUNT 100
int main(int argc, char *argv[])
{
    EmptyClassWiFlag * p1 = new (MEMCOUNT)EmptyClassWiFlag;
    cout<<"addrOfP1:"<<p1<<endl;
    cout<<"freeMemBegainAddr:"<<p1->freeMemBegain()<<endl;
    memset(p1->freeMemBegain(),0x00,MEMCOUNT-sizeof(EmptyClassWiFlag));
    char *flag = "hello world";
    memcpy(p1->freeMemBegain(),flag,strlen(flag)+1);
    cout<<(char*)p1->freeMemBegain()<<endl;
    return 0;
}

 成员变量char ch[0]起到了对象占用内存截至,剩余内存开头的作用。

 

 

 

 

猜你喜欢

转载自www.cnblogs.com/wangkeqin/p/11915092.html