[C++] placement new 的简要介绍

陪她去流浪 桃子 2017年06月10日 阅读次数:3122

在 C++ 中,动态创建一个新的对象所采用的常规语法是 Object* obj = new Object(); 这种形式。这种形式的 new 表达式 在编译器生成代码时会执行两个操作:

  1. 调用 operator new 函数分配原始内存(功能如 malloc);
  2. 调用 Object 类的构造函数 Object::Object()

如下面的例子:

#include <string>
#include <iostream>

class Object
{
public:
    Object(int n)
        : n(n)
    {
        std::cout << "Object() with n=" << n << std::endl;
    }

    ~Object()
    {
        std::cout << "~Object() with n=" << n << std::endl;
    }

    void dump() const
    {
        std::cout << "Object @ " << this << " with n=" << n << std::endl;
    }

protected:
    int n;
};

int main()
{
    Object* obj1 = new Object(626);
    obj1->dump();
    delete obj1;
}

将有类似下面的输出:

Object() with n=626
Object @ 00DAEF60 with n=626
~Object() with n=626

于是,这种形式的 new 有一个明显的缺陷:无法手动管理内存的分配,重载 operator new 可是强烈不建议的。

由于构造函数无法手动调用,如果我们需要手动管理 new 时的内存分配,应该怎么办呢?

这就会用到一种所谓的 placement new 语法:new (placement) Object();

这种形式的语法可不常见。其中括号内的 placement 即是我们手动指定的内存地址。 这种形式的 new 不会自动申请内存,而是直接使用我们提供的(不管值是多少,NULL 也是可以的),所以我们需要保证内存有效,并且大小合适(不能小于对象的字节数)。 然后在该内存上调用对象的构造函数来构造对象。由于内存是我们提供的,我们将不能直接 delete 掉该对象。而是 手动调用析构函数,然后手动回收内存(如果有必要)。

举个例子:

int main()
{
    // 在栈上分配至少 sizeof(Object) 大小的空间
    char mem[sizeof(Object)];

    std::cout << "mem: " << (void*)&mem[0] << std::endl;

    std::cout << "---------------------------------------" << std::endl;

    // 然后采用 placement new 语法在此内存上构造对象
    Object* obj1 = new (mem) Object(220);

    // 打印 obj 的地址,可以看到它就是 mem 的地址
    std::cout << "obj: " << obj1 << std::endl;

    obj1->dump();

    // 内存不是 new 创建的,不要直接调用 delete!
    // 而是手动调用析构函数来销毁对象,由于是栈上内存,让其自动释放
    obj1->~Object();

    std::cout << "---------------------------------------" << std::endl;

    // 调用析构函数后,只是对象被析构了,但内存还是在的
    // 可以直接在该内存上再次构造另一个对象
    Object* obj2 = new (mem) Object(626);
    obj2->dump();
    obj2->~Object();

    std::cout << "---------------------------------------" << std::endl;

    // 当然,手动分配内存也是可以的
    void* p = new char[sizeof(Object)];

    Object* obj3 = new (p) Object(123);
    obj3->dump();
    obj3->~Object();

    // 这个内存就需要我们自己释放了,因为是自己申请的
    delete[] p;
}

将有类似下面的输出,注意它们的地址全部是一样的:

mem: 008FFA08
---------------------------------------
Object() with n=220
obj: 008FFA08
Object @ 008FFA08 with n=220
~Object() with n=220
---------------------------------------
Object() with n=626
Object @ 008FFA08 with n=626
~Object() with n=626
---------------------------------------
Object() with n=123
Object @ 02D95B28 with n=123
~Object() with n=123

例子也举了几个了,话说 placement new 用在什么场合?

一言以蔽之:需要手动管理内存的地方!。比如:

  • 对象内存池,要知道,频繁分配小内存是会导致内存碎片严重,而且分配效率非常低下;
  • 标准库,标准库就大量运用了内存池,不过它有另外一个名字:Allocator。

这篇文章的内容已被作者标记为“过时”/“需要更新”/“不具参考意义”。

标签:C++ · 内存管理