玩命加载中 . . .

19.1-控制内存分配


19.1.1 重载new和delete

string *sp = new string("a value");
string *arr = new string[10];

一条new表达式,实际执行了三步操作

  1. new表达式调用一个名为operator new(或operator new[])的标准库函数,该函数分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象(或数组)
  2. 编译器运行相应的构造函数以构造这些对象,并为其传入初始值
  3. 对象被分配了空间并构造完成,返回一个指向该对象的指针
delete sp;
delete[] arr;

一条delete表达式,实际执行了两步操作

  1. sp所指的对象或arr所指的数组中的元素执行相应的析构函数
  2. 编译器调用名为operator delete(或者operator delete[])的标准库函数释放内存空间

可以重载operator newoperator delete函数,但是无法改变new运算符和delete运算符的基本含义

如果不使用自定义的newdelete,可以使用作用域运算符令newdelete表达式忽略定义在类中的函数,即::new::delete

operator new函数的返回类型必须是void*,第一个形参的类型必须是size_t且该形参不能含有默认实参,调用时,把存储指定类型对象所需的字节数传给size_t形参。如果是operator new[],则传递存储数组中所有元素所需的空间

operator delete函数的返回类型必须是void,第一个形参的类型必须是void*

operator newoperator delete函数需要调用mallocfree函数进行内存分配和释放

void* operator new(size_t size) {
    if (void *mem = malloc(size))
        return mem;
    else
        throw bad_alloc();
}
void operator delete(void *mem) noexcept {  // 不能抛出异常
    free(mem);
}

直接调用析构函数会销毁对象,但是不会释放内存

19.1.2 定位new表达式(placement new)

对于operator new分配的内存空间,我们无法直接调用构造函数来构造对象,可以使用new的定位new形式构造对象,定位new允许我们在一个特定的、预先分配的内存地址上构造对象

placement new的形式如下

new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]

place_address必须是一个指针,同时在initializers中提供一个以逗号分隔的初始值列表,该初始值将用于构造新分配的对象

比如下面这样

class A {
public:
    int id;
    A(): id(0) { cout << "default ctor, this=" << this << " id=" << id << endl; }
    A(int i): id(i) { cout << "ctor, this=" << this << " id=" << id << endl; }
    ~A() { cout << "dtor, this=" << this << " id=" << id << endl; }
};

char *buf = new char[sizeof(A)];    // 先分配内存,相当于char[4],得到一个指针
A *pc = new(buf) A(1);   // 用定位new调用构造函数
// ctor, this=0x5644905d2e70 id=1

placement new内部的过程分成三步

A *pc;
void *mem = operator new(sizeof(A), buf);   // 直接返回传入的指针
pc = static_cast<A*>(mem);  // 指针类型转换为A*
pc->A::A(1);        // 调用构造函数

里面第一步也是调用operator new,但它不是分配内存,只是简单地返回指针参数,类似于下面这样

void* operator new(size_t size, void* loc) {
    return loc; // 直接将传入的指针返回
}

应用的话,比如调用new[]时只能调用默认构造函数,如果我们想用自定义的构造函数来构造对象,就可以使用placement new来调用自定义构造函数

int main() {
    A *buf = new A[3];
    A *tmp = buf;
    for (int i = 0; i < 3; i++) {
        new(tmp++) A(i);     // placement new调用构造函数
    }
    delete[] buf;
    return 0;
}
default ctor, this=0x565188649e78 id=0
default ctor, this=0x565188649e7c id=0
default ctor, this=0x565188649e80 id=0
ctor, this=0x565188649e78 id=0
ctor, this=0x565188649e7c id=1
ctor, this=0x565188649e80 id=2
dtor, this=0x565188649e80 id=2
dtor, this=0x565188649e7c id=1
dtor, this=0x565188649e78 id=0  // 析构和构造的顺序是相反的

定位new使得我们可以使用栈或静态存储区的内存空间

#include <iostream>
#include <new>
using namespace std;

const int N = 4;

int main() {
    char buffer[512];
    cout << (void*)buffer << endl;

    double *pd = new(buffer)double[N];
    for (int i = 0; i < N; i++) {
        pd[i] = i;
    }
    for (int i = 0; i < N; i++) {
        cout << pd[i] << ": " << &pd[i] << endl;
    }
    return 0;
}

0x7ffd8418fa90
0: 0x7ffd8418fa90
1: 0x7ffd8418fa98
2: 0x7ffd8418faa0
3: 0x7ffd8418faa8

这里的空间是在栈上分配的,只是使用定位new对空间进行赋值


文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录