Loading

C++中的operator new与operator delete

什么是 operator newoperator delete

C++的 new 会做两件事:

  1. 调用 operator new 分配内存(实际上会调用 malloc 函数)。
  2. 调用构造函数构造对象。

比如:

Complex *c = new Complex(1, 2);

上面的构造函数会被编译器转化为:

void *mem = operator new(sizeof(Complex));
c = static_cast<Complex*>(mem);
c->Complex::Complex(1, 2);

delete 也会做两件事:

  1. 调用析构函数。
  2. 调用 operator delete 释放内存(实际上会调用 free 函数)。

比如:

Complex *c = new Complex(1, 2);
delete c;

上面的 delete 会被编译器转化为:

Complex::~Complex(c);
operator delete(c);

重载 operator newoperator delete

我们可以重载 operator newoperator delete 来接管系统默认的分配内存和释放内存的行为。

有两种重载方式:

1. 全局重载

重载全局的 ::operator new::operator delete::operator new[]::operator delete[]

比如:

void *mymalloc(size_t size) {
    return malloc(size);
}
void myfree(void *ptr) {
    return free(ptr);
}

void *operator new(size_t size) {
    std::cout << "customize global operator new" << std::endl;
    return mymalloc(size);
}
void *operator new[](size_t size) {
    std::cout << "customize global operator new[]" << std::endl;
    return mymalloc(size);
}
void operator delete(void *ptr) {
    std::cout << "customize global operator delete" << std::endl;
    return myfree(ptr);
}
void operator delete[](void *ptr) {
    std::cout << "customize global operator delete[]" << std::endl;
    return myfree(ptr);
}

重载之后,它的影响是全局性的,所有的 operator newoperator deleteoperator new[]::operator delete[] 都会走我们重载之后的函数。要慎用

2. 类内重载

要想单独的控制某一个类的内存分配与释放的行为,应该在类内重载 operator newoperator deleteoperator new[]::operator delete[]

比如:

class Foo {
public:
    void *operator new(size_t size) {
        std::cout << "customize member operator new" << std::endl;
        return mymalloc(size);
    }
    void *operator new[](size_t size) {
        std::cout << "customize member operator new[]" << std::endl;
        return mymalloc(size);
    }
    void operator delete(void *ptr) {
        std::cout << "customize member operator delete" << std::endl;
        return myfree(ptr);
    }
    void operator delete[](void *ptr) {
        std::cout << "customize member operator delete[]" << std::endl;
        return myfree(ptr);
    }
};

placement new

在类内也可以重载 operator new ,写出多个版本,前提是它们必须具有不同的参数列,其中第一个参数列的类型必须是 size_t ,其余参数以 new 关键字所指定的 place arguments 为初值。出现在 new() 中的参数便是 place arguments ,比如:

Foo *f = new(100, a) Foo(5);

上面 new(100, a) 中的 100、a 都是 place arguments这种带有参数的 new 称为 placement new

在类内也可以重载多个 operator delete ,但它们不会被 delete 所调用,只有在 new 所调用的构造函数抛出异常时,才会调用相应的重载版本的 operator delete ,来释放未成功创建的对象的内存。但是,下面的 Foo *f5 = new(100) Foo(1) 会调用一个抛出异常的构造函数,相应的重载版本的 operator delete 却没有被调用,为什么?没搞明白。

class Foo {
public:
  Foo() : m_i() {
    std::cout << "default constructor Foo()" << std::endl;
  }
  explicit Foo(int i) : m_i(i) {
    std::cout << "customize constructor Foo(int)" << std::endl;
    throw std::exception();
  }

  // ①
  void *operator new(size_t size) {
    std::cout << "operator new(size_t size)" << std::endl;
    return malloc(size);
  }
  // ①
  void operator delete(void *ptr, size_t size) {
    std::cout << "operator delete(void *ptr, size_t size)" << std::endl;
  }


  // ②
  void *operator new(size_t size, void *start) {
    std::cout << "operator new(size_t size, void *start)" << std::endl;
    return start;
  }
  // ②
  void operator delete(void *ptr, void *start) {
    std::cout << "operator delete(void *ptr, void *start)" << std::endl;
  }


  // ③
  void *operator new(size_t size, long extra) {
    std::cout << "operator new(size_t size, long extra)" << std::endl;
    return malloc(size + extra);
  }
  // ③
  void operator delete(void *ptr, long extra) {
    std::cout << "operator delete(void *ptr, long extra)" << std::endl;
  }


  // ④
  void *operator new(size_t size, long extra, char init) {
    std::cout << "operator new(size_t size, long extra, char init)" << std::endl;
    return malloc(size + extra);
  }
  // ④
  void operator delete(void *ptr1, long extra, char init) {
    std::cout << "operator delete(void *ptr1, long extra, char init)" << std::endl;
  }

private:
  int m_i;
};
int main() {
  Foo start;
  std::cout << std::endl;

  Foo *f1 = new Foo;
  std::cout << std::endl;

  Foo *f2 = new(&start) Foo;
  std::cout << std::endl;

  Foo *f3 = new(100) Foo;
  std::cout << std::endl;

  Foo *f4 = new(100, 'a') Foo;
  std::cout << std::endl;

  Foo *f5 = new(100) Foo(1);
  std::cout << std::endl;
  return 0;
}

一些小问题

1. 在使用 new 创建一个数组时,传给 void *operator new[](size_t size) 的参数到底是多大?

比如,现在有一个类 Foo ,使用 sizeof(Foo) 查看其内存大小为 48 字节,现在创建一个大小为 5 的 Foo 数组。

class Foo {
public:
    int a;
    double b;
    std::string c;
    void *operator new[](size_t size) {
        std::cout << "size = " << size << std::endl;
        return malloc(size);
    }
};
int main() {
    std::cout << sizeof(Foo) << std::endl;
    Foo *array = new Foo[5];
    return 0;
}

传入 void *operator new[](size_t size)size 大小为 48 * 5 + 8,这多出来的 8 个字节,用来存放这块数组的大小是 5 。如下图所示:

image-20220619203325318

2. operator newoperator delete 的调用顺序?

先尝试调用类内重载的 operator newoperator delete

再尝试调用全局的 operator newoperator delete

也可以显式的指定调用全局的 operator newoperator delete ,比如下面这样:

Foo *f1 = ::new Foo;
posted @ 2022-06-19 00:03  cclemontree  阅读(469)  评论(0)    收藏  举报