真实面经题目 · 原创解析

C++ 如何限制类对象只能在堆上或栈上创建?

C++ 限制对象只能在堆上或栈上创建,本质是控制构造、析构和释放入口。只能堆上创建通常把析构函数设为 private 或 protected,并提供 destroy 接口;只能栈上创建通常禁用 operator new 和 operator new[],阻止动态分配。

出现于:字节跳动 · 客户端

60 秒回答模板

限制只能在堆上创建,可以把析构函数设为 private 或 protected,让外部无法在栈对象离开作用域时调用析构,也无法直接 delete;同时提供静态 create 返回指针或智能指针,并提供 destroy、定制 deleter 或友元删除器来释放。限制只能在栈上创建,常见做法是把类的 operator new 和 operator new[] 声明为 deleted 或 private,这样外部无法 new 这个类,只能定义自动变量或作为成员对象。要注意 C++ 没有绝对封死所有构造路径的万能手段,placement new、友元、继承、工厂和容器成员都会影响约束边界,面试中重点说明控制分配入口和析构可访问性。

考点 堆限定靠析构权限
难度 真实面经高频题
回答目标 讲清机制、边界和追问

深入解析

01

控制对象创建位置的本质

C++ 对象生命周期由内存分配、构造、析构和内存释放几步组成。栈对象由编译器在作用域结束时自动析构,堆对象通常由 new 分配并由 delete 触发析构和释放。想限制对象位置,就要控制哪些代码能调用构造、析构、operator new 和 operator delete。单独把构造函数设为 private 只能强制走工厂,不等于区分堆和栈。

02

只允许堆上创建

常见做法是让析构函数不可被外部访问,例如 private 或 protected。这样外部不能声明普通栈对象,因为栈对象离开作用域时编译器需要能访问析构函数;也不能直接 delete 指针,因为 delete 同样需要访问析构函数。类再提供 create 方法在内部 new 对象,并提供 destroy 方法、静态删除函数或智能指针定制 deleter,确保对象能被受控释放。

03

堆对象释放设计

只允许堆上创建时,释放接口比创建接口更重要。如果只提供 raw pointer create 而忘记 destroy,就会把泄漏风险交给调用方。现代 C++ 更推荐返回 unique_ptr<T, Deleter> 或 shared_ptr<T>,删除器由类或友元持有访问析构函数的权限。这样既能阻止外部随意 delete,又能让 RAII 自动释放对象,避免人工 destroy 遗漏。

04

只允许栈上创建

限制堆上创建通常通过禁用 operator new 和 operator new[] 实现,例如将它们声明为 delete。这样外部写 new T 或 new T[] 会编译失败,但仍可以在栈上定义 T obj,也可以作为其他对象的成员被构造。需要同时考虑 nothrow new、对齐版本 operator new 和数组 new,否则在较严格设计中可能留下动态分配入口。

05

边界和例外

这类限制更多是接口层面的约束,不是安全沙箱。placement new 可以在调用方提供的内存上构造对象,友元或成员函数仍可能访问私有析构,派生类和容器也可能改变生命周期形态。面试中应说明常规工程做法和适用边界:堆限定适合引用计数、框架托管或自销毁对象;栈限定适合轻量 RAII guard、不可跨作用域持有的资源句柄或禁止异步逃逸的对象。

易错点

  • 认为私有构造函数就能区分堆对象和栈对象,忽略工厂内部仍能选择存储位置。
  • 只提供 create 不提供可靠 destroy 或智能指针删除器,导致堆限定类更容易泄漏。
  • 只删除 operator new,忘记 operator new[]、nothrow 或对齐分配等入口。
  • 把这类技巧说成绝对无法绕过,没有说明 placement new 和友元权限等边界。

面试官追问

只把构造函数设为 private 能限制只能堆上吗?

不能。private 构造只能阻止外部直接构造,通常需要工厂创建,但工厂内部仍可以选择栈、堆或静态存储。限制堆上更关键的是析构权限和释放接口。

私有析构后怎么用智能指针?

可以提供类内部创建的智能指针,并配置有权限访问析构函数的定制删除器;删除器可以是友元,也可以是类的静态成员函数。

禁用 operator new 后还能作为成员变量吗?

可以。禁用的是对这个类直接执行动态分配表达式,不影响它作为栈对象、全局对象或其他对象成员被构造。

为什么要同时考虑 operator new[]?

如果只禁用单对象 new,调用方仍可能用 new T[] 创建数组对象。要限制堆分配,数组版本和可能的对齐版本也要纳入设计。