昨天面试完,感觉自己有好多不足啊,亡羊补牢,女娲补天,哈哈哈

在 C++ 中,placement new 是一种特殊的 new 运算符变体,允许在预分配的内存地址上构造对象,而不分配新的内存。与标准 new 不同,placement new 仅负责调用构造函数,不负责分配内存。这使得它在需要精确控制内存分配的场景(如内存池、自定义容器或嵌入式系统)中非常有用。

1. 定义

Placement new 是 new 运算符的一种重载形式,其核心思想是将对象的构造与内存分配分离。标准 new 运算符的工作流程是:

  1. 通过 operator new 分配内存。
  2. 调用对象的构造函数初始化内存。

而 placement new 跳过第一步,直接在用户提供的内存地址上调用构造函数。其标准语法定义在 <new> 头文件中:

1
void* operator new(std::size_t size, void* ptr) noexcept;
  • size:要构造的对象大小(通常由编译器自动传递)。
  • ptr:用户提供的内存地址,用于构造对象。

2. 语法

Placement new 的基本语法如下:

1
new (address) Type(constructor_args)
  • address:指向预分配内存的指针,必须有足够大小且正确对齐。
  • Type:要构造的类型。
  • constructor_args:传递给构造函数的参数(可选)。

3. 工作原理

  • 内存分配:用户必须提供一块足够大小且正确对齐的内存(通常通过 newmalloc 或静态分配)。
  • 对象构造:Placement new 调用指定类型的构造函数,在提供的内存地址上构造对象。
  • 内存管理:Placement new 不负责分配或释放内存,内存的分配和释放由用户管理。
  • 析构:Placement new 不自动调用析构函数,用户需要手动调用 obj->~Type() 来销毁对象。

4. 关键特性

  • 不分配内存:仅调用构造函数,内存必须预先分配。
  • 灵活性:允许在任意内存位置构造对象(如栈、堆或自定义内存池)。
  • 性能优化:避免动态内存分配的开销,适用于高性能场景。
  • 手动管理:用户负责内存分配、释放和对象析构,增加了代码复杂性。

5. 使用场景

Placement new 在以下场景中特别有用:

  1. 内存池
    • 在预分配的内存池中构造对象,避免频繁的动态内存分配,减少内存碎片。
    • 示例:游戏引擎或实时系统中使用内存池管理对象。
  2. 自定义容器
    • 标准库容器(如 std::vector)使用 placement new 在预分配的缓冲区上构造元素。
    • 示例:std::vectorpush_back 可能在预分配的数组上使用 placement new 构造新元素。

6. 注意事项

使用 placement new 时需格外小心,以下是关键注意点:

  • 内存大小:提供的内存必须足够容纳对象(sizeof(Type))。

  • 内存对齐

    • 内存地址必须满足类型的对齐要求(alignof(Type))。

    • C++11 引入的 std::aligned_allocstd::align 可帮助对齐内存。

    • 示例:

      1
      2
      void* raw = std::aligned_alloc(alignof(MyClass), sizeof(MyClass));
      MyClass* obj = new(raw) MyClass();
  • 析构函数:Placement new 不自动调用析构函数,必须手动调用 obj->~Type()

  • 内存释放:用户负责释放原始内存,需确保在对象析构后正确释放。

  • 异常安全

    • 如果构造函数抛出异常,需确保内存不会泄漏。
    • 使用智能指针或 RAII 机制管理内存可提高安全性。
  • 不可重复构造:在同一内存地址上重复调用 placement new(未先析构)会导致未定义行为。

7. 高级示例:实现简单内存池

以下是一个使用 placement new 实现简单内存池的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <vector>

class MemoryPool {
public:
MemoryPool(size_t size) : buffer_(new char[size * sizeof(MyClass)]), capacity_(size) {}
~MemoryPool() { delete[] buffer_; }

MyClass* allocate(int value) {
if (used_ >= capacity_) return nullptr;
// 在 buffer_ 上构造对象
MyClass* obj = new(buffer_ + used_ * sizeof(MyClass)) MyClass(value);
used_++;
return obj;
}

void deallocate(MyClass* obj) {
if (obj) obj->~MyClass(); // 手动调用析构函数
}

private:
char* buffer_;
size_t capacity_;
size_t used_ = 0;
};

class MyClass {
public:
MyClass(int value) : value_(value) { std::cout << "Constructed with " << value_ << "\n"; }
~MyClass() { std::cout << "Destructed\n"; }
int value_;
};

int main() {
MemoryPool pool(2); // 可容纳 2 个 MyClass 对象
MyClass* obj1 = pool.allocate(10);
MyClass* obj2 = pool.allocate(20);
std::cout << "obj1 value: " << obj1->value_ << "\n";
pool.deallocate(obj1);
pool.deallocate(obj2);
return 0;
}

输出

1
2
3
4
5
Constructed with 10
Constructed with 20
obj1 value: 10
Destructed
Destructed