【genius_platform软件平台开发】第二十九讲:new关键字、new运算符、placemen new详解

在这里插入图片描述

  • 最近在和朋友讨论一些技术的时候,提到了placemen new来提高内存一次性申请终身适用的高效问题。所以趁这个机会给大家顺利下几个new相关的技术。

直接上代码里面有讲解

// C++new运算符.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
using namespace std;

#define operator_new_delete 0
#define placement_new 1

/* 技术要点
* ★★★ new运算符和operator new之间的区别
* 1. new: 指我们在C++里通常用到的关键字。比如 A *a=new A,为了实现这一目的,应用程序需要重载new运算符和delete运算符以控制内存分配的过程。
*         对于new来说,有new和::new之分,前者位于std命名空间中;
* 2. operator new: 是一个标准库函数,并不是运算符。对于operator new 来说,分为全局重载和类重载。
*         事实上系统默认的全局重载也只是调用malloc分配内存,并且返回一个void* 指针;
*
* ★★★ new运算符和operator new之间的联系
* 1. MyClass* p1 = new MyClass; 这行代码实际执行三步操作:
*   1.1 new表达式调用一个名为operator new(或者operator new[])的标准库函数。该函数分配一块足够大的、原始的、未命名的内存空间以便存储A类型的对象(或者对象数组);
*   1.2 编译器运行相应地构造函数以构造这些对象,并为其传入初始值;
*   1.3 对象被分配了空间并构造完成,返回一个指向该对象的指针;
*   如果类MyClass重载了operator new,那么将调用MyClass::operator new(size_t size),如果没有重载,就调用全局函数::operator new(size_t size),全局new操作符由c++默认提供
*
* 2. operator new接口和operator delete接口
* 2.1 标准库定义了以下8个版本。其中前4个版本可能抛出bad_alloc异常,后4个版本不会抛出异常;
*     这些版本可能抛出异常
*
*     第一种分配size个字节的存储空间,并将对象类型进行内存对齐。如果成功,返回一个非空的指针指向首地址。失败抛出bad_alloc异常
*     void* operator new (size_t);
*     void* operator new[] (size_t);
*     void* operator delete (void*) noexcept;
*     void* operator delete[] (void*) noexcept;
*
*     第二种在分配失败时不抛出异常,它返回一个NULL指针
*     void* operator new (size_t, nothrow_t&)  noexcept;
*     void* operator new[] (size_t, nothrow_t&) noexcept;
*     void* operator delete (void*, nothrow_t&) noexcept;
*     void* operator delete[] (void*,nothrow_t&) noexcept;
*
*     第三种是placement new版本,它本质上是对operator new的重载,定义于#include <new>中。它不分配内存,调用合适的构造函数在ptr所指的地方构造一个对象,之后返回实参指针ptr
*     void* operator new (std::size_t size, void* ptr) throw();
*
*     第一、第二个版本可以被用户重载,定义自己的版本,第三种placement new(定位new)不可重载
*     MyClass* pMc = new MyClass;                       //调用第一种
*     MyClass* pMc = new(std::nothrow) MyClass;         //调用第二种
*     new (ptr)MyClass();                               //调用第三种
*     new (ptr)MyClass()调用placement new之后,还会在ptr上调用MyClass::MyClass(),这里的ptr可以是堆中动态分配的内存,也可以是栈中缓冲
*     对于operator new函数:要实现不同的内存分配行为,应该重载operator new,而不是new
* 2.2 如果我们想要继续使用默认的operator new,就应该写成::new ,字面意思是调用最外层命名空间中的operator new。
*     值得一提的是,全局operator new也是可以被重载的。通过这种方式,我们可以改变所有new的部分行为
*
* ★★★ new和delete是否需要一定成对使用???
* 1. 基础类型:
*       可以不用成对
* 2. 自定义类型:
*       需要成对
*
* ★★★ placemen new定位new表达式
* 1. 功能:
*       定位new允许我们在一个特定的、预先分配的内存地址上构造对象,实参不局限于动态内存
* 2. 形式:
*       new  ( place_address )  type
*       new  ( place_address )  type (initializers)
*       new  ( place_address )  type [size]
*       new  ( place_address )  type [size]  { braced initializer list }
*       其中place_address必须是一个指针,同时在initializers中提供了一个(可能为空的)以逗号分隔的初始值列表,该初始值列表将用于构造新分配的对象
*3. 当仅通过一个地址值调用时,定位new使用operator new(size_t)分配的内存。这是一个我们无法重载的operator new版本。该函数不分配任何内存,它只是简单地返回指针实参;
*   然后由new表达式负责在指定的地址初始化对象以完成整个工作
*   eg.
*     MyClass* ptr = MyClass char[10];              //allocate memory
*     MyClass* pMc = new(ptr)MyClass;               //construct in allocated storage ("place")
*     pMc->~T();                                    //destruct
*     delete[] ptr;                                 //deallocate memory
*
* ★★★ operator new\delete重载形式
* 1. 成员函数
*   1.1 void* className::operator new( size_t size );
*   1.2 void className::operator delete( void *ptr);
*   1.3 void* className::operator new[](size_t size);
*   1.4 void className::operator delete[](void* ptr);
*
* 2. 全局函数
*   1.1 void* operator new( size_t size );
*   2.2 void operator delete( void *ptr);
*   2.3 void* operator new[](size_t size);
*   2.4 void operator delete[](void* ptr);
*
* 3. 构造析构顺序
*   3.1 new对象
*       operator new ---> constructor
*   3.2 delete对象
*       destructor ---> operator delete
*
* 在重载 new 或 new[] 时,无论是作为成员函数还是作为全局函数,它的第一个参数必须是 size_t 类型。
* size_t 表示的是要分配空间的大小,对于 new[] 的重载函数而言,size_t 则表示所需要分配的所有空间的总和
* size_t 在头文件 <cstdio> 中被定义为typedef unsigned int size_t;,也就是无符号整型
* 当然,重载函数也可以有其他参数,但都必须有默认值,并且第一个参数的类型必须是 size_t
* 如果类中没有定义 new 和 delete 的重载函数,那么会自动调用内建的 new 和 delete 运算符
*/

// 基类
class MyClass
{
public:
    MyClass() { m_nSize = 2; cout << "MyClass::MyClass struction..." << endl; };
    MyClass(int nSize) { m_nSize = nSize; cout << "MyClass::MyClass(int nSize) struction..." << endl; };
    virtual ~MyClass() { cout << "~MyClass::MyClass destruction..." << endl; };

    void show() { cout << "MyClass::show m_nSize=" << m_nSize << endl; };

#if (operator_new_delete == 1)
    // 以成员函数形式
    // 重载new运算符
    void * operator new(size_t size);
    // 重载delete运算符
    void operator delete(void* p);
    // 重载new[]运算符
    void * operator new[](size_t size);
    // 重载delete[]运算符
    void operator delete[](void* p);
    // 重载
    void* operator new(size_t size, string str)
    {
        cout << "operator new size " << size << " with string " << str << endl;
        return ::operator new(size);
    }
    // 重载
    void operator delete(void* pointer)
    {
        cout << "operator delete" << endl;
        ::operator delete(pointer);
    }
#endif

private:
    int m_nSize;
};

#if (operator_new_delete == 1)
// 重载new运算符
void* MyClass::operator new(size_t size)
{
    cout << " MyClass::operator new" << " size=" << size << endl;
    return malloc(size);
}

// 重载new[]运算符
void* MyClass::operator new[](size_t size)
{
    cout << " MyClass::operator new[]" << " size=" << size << endl;
    return malloc(size);
}

// 重载delete运算符
void MyClass::operator delete(void* ptr)
{
    cout << " MyClass::operator delete" << " ptr=" << ptr << endl;
    free(ptr);
}

// 重载delete[]运算符
void MyClass::operator delete[](void* ptr)
{
    cout << " MyClass::operator delete[]" << " ptr=" << ptr << endl;
    free(ptr);
}
#endif

//
//
int _tmain(int argc, _TCHAR* argv[])
{
    // new 和 delet 是否需要成对使用
    // 1. 基础类型
    int* pInt = new int[10];
    delete pInt;
    pInt = NULL;
    // 2. 基础类型
    MyClass* pMyClass = new MyClass[10];
    delete pMyClass;
    pMyClass = NULL;

    // 验证operator new/delete
    MyClass* p1 = new MyClass();
    delete p1;

    MyClass* p2 = new MyClass[2];
    delete[] p2;

#if (operator_new_delete == 0)
    // 验证placement new
    // 栈空间
    char szBuff[100];
    szBuff[0] = 'A';
    szBuff[1] = 'B';
    szBuff[2] = 'C';
    szBuff[3] = 'D';
    cout << "szBuff= "<<(void*)szBuff << endl;
    MyClass* p3 = new(szBuff)MyClass;
    cout << "p3= " << (void*)p3 << endl;
    p3->show();
    
    // 堆空间
    char* pBuff = new char[10 * sizeof(MyClass)];
    cout << "pBuff= " << (void*)pBuff << endl;
    MyClass *p4 = new(pBuff)MyClass(10);
    cout << "p4= " <<p4 << endl;
    p4->show();
    p4->~MyClass();
    delete[]pBuff;
#endif

    system("pause");

  return 0;
}

new和delete是否需要成对使用,具体看操作的数据类型

  • 因为new[]会创建一个数组,一个对象数组需要一定的空间大小,假设一个对象需要N字节大小,K个对象的数组就需要K*N个空间来构造对象数组,但是在delete[]时候,如何知道数组的长度呢?

  • 所以new[]会在KN个空间的基础上,头部多申请4个字节,用于存储数组长度,这样delete[]时候才知道对象数组的大小,才会相应调用K次析构函数,并且释放KN+4大小的内存。

  • 1. 基础数据类型可以不用成对,正常调用。
  • 2. 自定义数据类型则需要成对使用,否则报错。
    在这里插入图片描述
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

隨意的風

如果你觉得有帮助,期待你的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值