C语言IT狗工作室C语言&嵌入式

重载C ++中的new和delete运算符

2019-10-30  本文已影响0人  铁甲万能狗

有时我们,C++编译器提供的默认版本的new和delete运算符,无法在他们执行操作之前,对被操作的对象的数据成员执行一些自定义的逻辑操作,那么我们此时就需要考虑重载C ++中的new操作符和delete操作符。 它们操作符可以全局重载,也可以在特定类中重载。

首先我们为什么要重载new / delete操作符?

  1. 可以在重载新的运算符功能中添加异常处理例程。
  2. 希望自定义运算符delete,以用0覆盖被回收的堆内存块,以提高应用程序数据的安全性(下文示例会提及)
  3. 重载new操作符可以在其内部定义C版本的malloc或realloc函数进行对象的堆内存分配,然而C++并不建议你这样做,因为这样已经绕过了标准库中默认的内存分配器的内存管理机制。
  4. 同理delete操作也可以在其重载版本中定义C版本的free()函数,同样C++是不建议这么做。

new / delete 操作符的作用域

以下是new操作符函数的原型

void* operator new(size_t size);

以下是delete操作符函数的原型
delete操作符必须匹配一个void*类型的参数,函数返回的类型是void,并且默认情况下,重载后的new和delete操作符函数都是静态成员,因此在函数内部是无法使反问this指针

void operator delete(void*); 

该函数接收一个必须删除的void *类型的参数。 函数不应该返回任何东西。
注意:默认情况下,重载的new和delete运算符函数都是静态成员。 因此,他们无权访问此指针。

重载类内部的delete操作符

下面是一个关于Person类的个人信息的例子,我们在Person类内部重载了delete操作符

class Person{
    double d_height;
    size_t d_age;
    std::string d_idNo;
    std::string d_name;
    bool d_secur=false;
    
public:
        Person(std::string& name,size_t age,double height,
                        std::string id,bool secur=false)
        :  
         d_name(name),
        d_age(age),
         d_height(height),
         d_secur(secur),
         d_idNo(id)
        {}

        void show(){
                std::cout<<"姓名:"<<d_name<<std::endl;
                std::cout<<"年龄:"<<d_age<<std::endl;
                std::cout<<"身高:"<<d_height<<std::endl;
                std::cout<<"身份证:"<<d_idNo<<std::endl;
        }

        void set_height(double height)
        {
            if(height>=0){d_height=height;}
        }

        void set_age(size_t age){
             if(age>0){d_age=age;}
        }

        void operator delete(void* var){
          Person *tmp=static_cast<Person*>(var);
          if(tmp==nullptr){
             std::cout<<"该对象不存在,请勿重复delete操作!!"<<std::endl;
             exit(0);
          }
          if(tmp->d_secur){
             std::cout<<"delete前对用户敏感信息重置为默认值"<<std::endl;
             tmp->d_name="";
             tmp->d_age=0;
             tmp->d_height=0;
             tmp->d_idNo="";
             tmp->show();
          }
          ::operator delete (var);
          var=nullptr;
       }

};

上面的重载delete操作符默认是一个静态函数,因此我们没有使用Person对象的this指针,那么我们需要在delete operator函数内部在delete之前,我们需要对被delete的对象内部的数据进行一些重置操作,该怎么办?
正如例子所示,我们使用函数原型中的void*指针参数进行类型转换为tmp临时变量,用该临时变量来重置我们Person对象内部的数据成员

Person *tmp=static_cast<Person*>(var);

然后,我们还有一些辅助方法用于修改Person对象的信息,并且在之后显式调用delete销毁Person对象

void do_something(Person **s,double k){
   if(*s==nullptr){
      std::cout<<"对象不存在,操作失败"<<std::endl;
      exit(0);
   }
   (*s)->set_height(k);
   (*s)->show();
   
   delete *s;
   *s=nullptr;
}

调用代码

int main(void){
   std::string name="Kali";
   std::string idnum="4738348-3848-2345";
   Person *st=new Person(name,19,95,idnum,true);
   Person *tmp=st;
   
   std::cout<<"st指向的堆内存地址:"<<st<<std::endl;
   do_something(&st,72);
   std::cout<<std::endl;

   //这里中间假设有很多业务代码......
   std::cout<<"程序员不经意再次调用之前已经释放的指针变量"<<std::endl;
   std::cout<<std::endl;
   do_something(&st,64);
}

输出

sss18.png

从程序的输出我们,我们已经看到重载delete操作符的用处了,delete操作符内部,我们使用了一个Person类的指针变量,在被内存释放前,该临时变量修改它指向堆中的内存块的数据,并且也通过“::”作用域操作符再次调用全局的operator操作符,如下代码所示

 ::operator delete (var);

最后对传入指针变量重置为nullptr,这样做的目地是为了防止该指针变量沦为幽灵指针,关于“幽灵指针”是设计内存泄漏的话题,我会在另外文章中详述。

后记:

上一篇 下一篇

猜你喜欢

热点阅读