• C++基础——new与delete


    本文目录

    专业名称

    new operator:new操作符,new表达式
    operator new:new运算符
    placement new:定位new
    delete operator:delete操作符,delete表达式
    operator delete:delete运算符
    delete没有placement版本 各个版本的解释 1. new operator

    new operator:new表达式

    实际上只是一个称呼,是三个操作的集合。

    • 申请内存(调用operator new)
    • 在内存上调用构造函数
    • 将申请的内存空间返回

    delete operator:delete表达式

    delete operator也是一个称呼,是两个操作的集合。

    • 调用析构函数
    • 调用operator delete释放空间

    operator new/delete:new/delete运算符

    • 分为::operator new与Class::operator new。一个是全局的,一个是类内部定义的。平时听到的new与delete重载,就是重载这里的new/delete。注意点如下。
      -- class中的new与delete是隐式静态的。也就是不管你有没有声明为static,它都是static的。
      -- 非static成员在没有实例存在的情况下是不能使用的。而当我们定义一个实例时,很明显此时实例还没有存在,这个时候不可能调用一个非static的成员,所以operator new/delete必须是static的。
      -- static成员是不会隐式传入this指针的,所以在operator new/delete中不能使用任何类的非静态成员。
      -- 非静态的成员函数在编译器会在argument list的第一个位置插入一个本身类型的this指针。所以一个函数名,参数列表与全局函数一模一样的函数,也不会出现二义性问题。但是一个static成员函数,是不会隐私插入this指针的,所以当我们在class中重载new/delete时,客户定义实例时需要确认是否在全局也定义了new,否则会产生二义性。举个栗子。
    //VS2013
    class StringClass
    {
    public:
        void *operator new(size_t size);
    };
    void *operator new(size_t size);
    void main()
    {
        //1. 调用operator new申请内存
        //2. 调用构造函数
        //二义性,同时存在两个可用的operator new
        StringClass *p = new StringClass;
    }
    

    -- PS:关于函数匹配规则,一开始接触觉得很简单,当某天触发了bug的时候就会“书到用时方恨少”,这方面推荐两个连接给大家。看这里还有这里

    • 关于operator new/delete标准库提供了8个版本,这些版本的new/delete我们都是可以重载的。如下。
    //八个版本中有2个new版本有可能抛出异常
    void* operator new(size_t t);
    void* operator new[](size_t t);
    void* operator delete(void*)noexcept;
    void* operator delete[](void*)noexcept;
    
    //nothrow_t为空结构体,用于区分上面与下面运算符
    void* operator new(size_t t,nothrow_t&)noexcept;
    void* operator new(size_t t,nothrow_t&)noexcept;
    void* operator delete(void *,nothrow_t&)noexcept;
    void* operator delete[](void *,nothrow_t&)noexcept;
    

    理论这么多,下面先来个栗子。

    //StringClass.h
    //自己定义的一个类
    #pragma once
    #include<iostream>
    using namespace std;
    class StringClass
    {
    public:
        StringClass(){
            cout << "string class construtor" << endl;
        }
        ~StringClass(){
            cout << "string class destrutor" << endl;
        }
        void *operator new(size_t size){
            cout << "Class::operator new(size_t) " << size << endl;
            void *ret = (void*)malloc(size);
            return ret;
        }
        static void operator delete(void *p){
            free(p);
            cout << "Class::operator delete(void*)" << endl;
        }
    };
    void main(){
        StringClass *p = new StringClass;
        delete p;
    }
    

    结果如下。在new表达式中,先调用了operator new,再调用constructor,最后将指针返回给p。最后在释放的时候,先调用了destructor,再调用了operator delete。

    既然使用到了delete,怀着“授人以鱼不如授人以渔”的想法,推荐读者了解一下crtdbg这个头文件,谷歌一下就知道了。

    placement new:定位new

    • 在堆里面申请内存,需要寻找空的内存,多次申请释放还会产生大量的内存碎片,但这个神器会帮你解决这些问题。使用它,你只需要申请一次内存,就能够多次创建不同的实例,具体参见下例的讲解。
    • 前面提到,new operator会有三个步骤,第一是申请内存,第二是调用构造函数,第三是返回指针。定位new,之所以为定位,是因为我们事先分配好了内存,并且指定了必须使用这块内存进行初始化。共有四种语法如下。
    new (place_address) type;
    new (place_address) type (initializers);
    new (place_address) type[size];
    new (place_address) type[size]{braced initializers list};
    

    对第一种我们举个栗子。

    #pragma once
    #include<iostream>
    using namespace std;
    
    class StringClass
    {
    public:
        StringClass(){
            cout << "string class construtor" << endl;
        }
        ~StringClass(){
            cout << "string class destrutor" << endl;
        }
        //比起前面的代码,增添了这个函数
        static void *operator new(size_t size, void *p)    {
            cout << "Class::operator new(size_t,void*)" << endl;
            return p;
        }
    };
    
    void main()
    {
        StringClass *buf = (StringClass*)malloc(sizeof(StringClass));
        memset(buf, 0, sizeof(StringClass));
        StringClass *p2 = new(buf) StringClass();
        p2->~StringClass();
        free(buf);
    }
    
    • 上面总共有五个步骤:
      -- 上述代码先使用申请了一块内存,大小用户根据自己需要去决定。
      -- 初始化这块内存。
      -- 然后在上面调用了StringClass的构造函数,再令p2指向他。
      -- 然后p2使用完毕在上面调用自己的析构函数。
      -- 程序结束前将这块内存释放。假如有需要创建另外一个对象,我们可以不用申请新的内存,继续使用这块内存。

    • 与operator new不同的是,使用placement new是无需定义operator delete的,如上,整个过程压根就没有用到它。相反,假如你在客户端使用了它,还会导致调用析构函数的多次调用。

    警告

    除了placement new外,其他版本的new与delete都要成对地重载。因为你重载了编译器的new,它不知道delete需要为你做些什么。

    总结

    • new总共分成3类,new operator是一个操作的集合,不是具体的运算符。可以重载的叫做operator new。
    • 使用了placement new,必须定义void *operator new(siez_t,void *p),不用定义operator delete。而除了placement new,若定义了operator new/delete需要成对定义。
    • 所有的operator new都是隐式静态的,为了让代码易于阅读,你应该加上static
    • 所有的operator new都应该返回void*

    最后的疑问

    在很多书籍以及博客中,都指出了下面这个operator new不能被重载。在C++Primer第五版,白色封面的那本,P727最下方中,明确指出了它不能重载,但实际上,使用placement new的时候我们却不得不重载这个版本。关于这一点,希望有高人能够给出意见

  • 相关阅读:
    php测试题整理(0519)
    Ajax调用返回json,xml数据类型(0517--pm)
    python 收集测试日志--格式
    python3下tomorow模块报语法错误def async(n, base_type, timeout=None): ^ SyntaxError: invalid syntax
    appium 下载
    VMware 虚拟机设置固定ip
    Centos7 安装配置 SVN
    【Linux】 Centos7 安装 mysql-8.0
    win7 SP1 64位 原版 百度网盘下载
    win10操作系统 64位 原版 百度网盘下载
  • 原文地址:https://www.cnblogs.com/suimeng/p/5295754.html
Copyright © 2020-2023  润新知