关于c++ new操作符的重载

C++基础部分 专栏收录该内容
33 篇文章 0 订阅

c++中,new的用法总结:

  1. new type(xxx) 分配一个type类型大小的内存空间,并以括号中的值xxx来初始化这个变量;
  2. new type[N] 分配N个type类型的内存空间,并用默认构造函数来初始化这些变量;
    示例
    int *a = new int[5];
    class A {…} //声明一个类 A
    A *obj1= new A; //先用operator new 分配空间,然后调用A的默认构造函数初始化空间
    A *obj2= new A(xx);//先用operator new 分配空间,然后调用A的对应构造函数初始化空间
    delete []a;
    delete obj1;
    delete obj2;

一、 关于c++ new操作符的重载

你知道c++ 的new操作符和operator new的区别么?也许你还会问,他们有区别吗?

当你写下面这样的代码时,

string *pa = new string("memory managerment");

你使用的是new操作符,这个操作符和sizeof一样,是c++语言级别支持的。你不能改变它的语义,它做的事情总是不变的:

  1. 分配足够的内存以容纳对象
  2. 然后调用构造函数初始化上一步所分配的内存。

New操作符总是做这两件事情,你不能改变它的行为。

你所能改变的只是第一步的行为,如何为对象分配RAW内存。operator new 函数用来为对象分配原始内存。New操作符的第一步调用的就是operator new。你可以重载这个函数。它的原型如下:

Void* operator new(size_t size);

函数的返回值是void*, 因为这个函数返回的是指针。这个指针指向原生的,为初始化的内存。其语义就像malloc。实际上它内部调用的就是malloc。参数size指定待分配的内存大小。你可以在重载的时候加上额外的参数,但是第一个参数类型必须是size_t.

绝大多数情况下,你不需要调用operator new,万一你需要调用它,调用的格式是这样的:

Void* rawmemory = operator new(sizeof(string));

函数operator new将返回一个指针,指向一块足够容纳一个string对象的内存。

就像malloc一样,operator new的唯一的职责就是分配内存,它对构造函数一无所知。
把operator new返回的未初始化的指针构造成一个对象是new操作符的工作。当你的编译器遇到以下的代码时:

string *pa = new string("memory managerment");

它生成的伪代码类以如下:

Void* memory = operator new(sizeof(string));

call string::string("memory managerment") on memory;

string* Pa = static_cast<string*>(memory);

第二步包含了构造函数的调用。这是你的编译器调用的。
那么你估计会问,程序员可以手动调用构造函数吗?答案是否定的。

但是编译器同时给你提供了另外一个折中,是你可以达到这个目的。

需要说明的是,在一个已存在的对象上调用构造函数是没有任何意义的。因为构造函数用来初始化对象。
但是有时候有一些内存已经被分配但是尚未初始化,你需要在这些内存中构造一个对象。你可以使用operator new函数的一个特殊版本。一个术语叫 placement new的函数来做这件事情。

回到前面那个字符串的例子,我们可以这样使用placement new

Void* memory = operator new(sizeof(string));

String* pa = new (memory) string("memory managerment");

以上两句就相当于new 操作符所做的事情了。

这就是operator newplacement new的全部秘密。一般的来说,你不需要重载和显式调用这两个函数。

二、常用的new 操作符(new operator)和operator new(size_t sizt)函数的区别

文章来自https://www.jianshu.com/p/a07ba8f384da

题外话:sizeof的用法

摘自https://baike.baidu.com/item/sizeof/6349467?fr=aladdin
sizeof是C/C++中的一个操作符(operator),简单的说其作用就是返回一个对象或者类型所占的内存字节数
其返回值类型为size_t,在头文件stddef.h中定义。这是一个依赖于编译系统的值,一般定义为

typedef unsigned int size_t;

sizeof有两种语法形式,如下:

sizeof(type_name);//sizeof(类型);
sizeof object;//sizeof对象;
int i;
sizeof(i);//ok
sizeof i;//ok
sizeof(int);//ok
sizeof int;//error

关于sizeof()的用法也有了一定的认识,今天遇到一题,我估计大家也会做错
求以下程序的输出结果,


#include <stdio.h>

int main()
{
    if(sizeof(int) - sizeof(double) < 0)
    {
        printf("<\n");
    }
    else
    {
        printf(">=\n");
    }

    return 0;
}

我们知道sizeof(int) = 4, sizeof(double) = 8, 则4 -8 = -4 ,则if(sizeof(int) - sizeof(double) < 0)的条件为真,所以,输出应该是打印出“<”。

发现和预期的结果相反,究其原因,原来sizeof()的返回值是一个无符号整型十进制数值,即:unsigend int 型,所以两个unsigend int型的数据进行运算后其结果只能是unsigend int型的,即大于等于0的一个整数值,所以,if()条件不满足,执行下一条语句。

正题

new operator 是先调用operator new函数来分配返回值为void*的内存,然后再调用作用类型的构造函数去初始化赋值这块内存。如string * str = new string("hello");就是对new表达式的常用方式。

new表达式(new operator)其实可以分解为两部,即先调用new操作符(operator new)申请内存,再调用placement new来初始化对象。即上面对new表达式的使用string * str = new string("hello");等价于下面两句。

void *buffer = ::operator new(sizeof(string));
buffer = new(buffer) string(“hello”);

而operator new函数相当于是malloc,其实它的内部实现也就是调用了void * malloc(size_t size);函数。

 void* operator new(size_t sizt)
{
      return malloc(size);      
}

C++当中3种new的用法

侯捷侯老师的著作《STL源码剖析》,书中第二张开始讲到了stl中的allocator的一个简易实现,发现原来cpp中的new其实除了我们平常用的最多的new表达式(new operator)之外,还有剩下两种用法。

  1. new operator/delete operator
    new operator,也叫new表达式,是我们在cpp中最常见的new的用法,比如:
    string *pStr = new string("abd");
    这个new表达式完成了两件事情:申请内存和调用类的构造函数初始化对象。
    与之相对应的是delete operator(delete 表达式),它完成了两件事情:调用所指向元素的析构函数,然后释放内存空间。

  2. operator new(operator new[])/operator delete(operator delete[])
    operator new(或者operator new[]),其实是标准库函数,容易和new operator搞混。类似与c当中的malloc库函数,只负责申请一块原始的未命名的内存空间,例如:

     void *buffer = ::operator new(sizeof(string));

operator delete(或者 operator delete[]),是标准库函数,类似于c当中free库函数,负责释放内存空间。

标准库定义了operator new函数和operator delete函数的8个重载版本(c++ primer 中文版第5版,P727),其中前4个版本可能抛出bad_alloc异常,后4个版本则不会抛出异常:

// 这4个版本可能抛出异常
void *operator new(size_t);         // 分配一个对象
void *operator new[](size_t);           // 分配一个数组
void *operator delete(void*) noexcept;      // 释放一个对象
void *operator delete[](void*) noexcept;    // 释放一个数组
// 这些版本承若不会抛出异常
void *operator new(size_t, nothorw_t &);        // 分配一个对象
void *operator new[](size_t, nothorw_t &);      // 分配一个数组
void *operator delete(void*, nothorw_t &) noexcept; // 释放一个对象
void *operator delete[](void*, nothorw_t &) noexcept;   // 释放一个数组

其中类型nothrow_t是定义在new头文件中的一个struct,在这个类型中不包含任何成员。noexcept异常说明符用来指定其不抛出异常。

当我们自己定义了(函数重载,函数名不变但是参数变)全局的operator new函数和operator delete函数后,编译器会优先使用我们自己定义的operator new和operator delete,这样我们就担负起了控制动态内存分配的职责(比如自己设计的内存池,内部就可以不用malloc来申请内存,甚至可以用全局变量的内存来分配),因此这两个函数必须是正确的。我们也可以定义它们为成员函数,如果同时定义了全局和类的成员函数,优先使用成员函数版本的,如果需要使用全局版本的,可以用作用于运算符::operator new和::operator delete。

  1. placement new
    placement new,也叫定位new表达式,它用于在给定的内存中初始化对象(但不分配内存),例如
void *buffer = ::operator new(sizeof(string));
buffer = new(buffer) string(“abd”);

与placement new类似,我们可以通过对象调用析构函数,也可以通过对象的指针或引用调用析构函数,这与调用其他成员函数没什么区别:

string *sp = new string("a value"); // 分配并初始化一个string对象
sp->~string();//调用析构函数会销毁对象,但是不会释放内存。

new表达式(new operator)其实可以分解为两部,即先调用new操作符(operator new)申请内存,再调用placement new来初始化对象。

来看一个例子,例子中对3个new的用法都做了举例:

#include <new>      // for placement new
#include <string>   // for string
#include <iostream> // for cout
using namespace std;
int main(void)
{
    void* buffer = ::operator new(sizeof(string));  // operator new
    buffer = new(buffer) string("123");             // placement new
    cout << *(string*)buffer << endl;
    // equals new operator
    string* str = new string("123");
    cout << *str << endl;
    delete str;
    ::operator delete(buffer);
    return 0;
}
  1. 什么时候需要用到operator new和placement new
    如果我们需要把对象的内存分配操作和对象的初始化操作分离开来,就需要用到operator new 和placement new。
    其实在stl里面allocator就用到了类似的做法,其中allocate负责向系统申请一块指定大小的内存(operator new),而construct负责在这块内存上根据模板类的类型T来调用T的构造函数初始化内存(placement new)。同理deallocat和destory负责释放内存和析构对象。下面是一个简单的allocator实现,画红线部分即为两种new和两种delete的用法。

在这里插入图片描述

  • 2
    点赞
  • 0
    评论
  • 4
    收藏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:书香水墨 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值