0

0

C++中如何限制类只能在堆上创建对象?(将析构函数设为私有)

尼克

尼克

发布时间:2026-01-15 13:57:42

|

297人浏览过

|

来源于php中文网

原创

将析构函数设为私有不能真正限制对象只在堆上创建,因为它会阻止智能指针析构、使移动语义失效,且无法解决堆对象安全销毁问题;正确做法是删除所有栈相关函数、保持析构函数public并提供返回智能指针的静态工厂方法。

c++中如何限制类只能在堆上创建对象?(将析构函数设为私有)

为什么把析构函数设为私有不能真正限制对象只在堆上创建

~ClassName() 设为 private 确实会让上对象编译失败(因为栈对象生命周期结束时,编译器需调用析构函数,而无法访问私有析构),但这个方案有严重漏洞:它同时阻止了所有自动资源管理行为,包括 std::unique_ptrstd::shared_ptr 的正常析构,也导致 move 语义失效。更关键的是,它没解决“如何安全销毁堆对象”的问题——用户只能靠裸指针 new + delete,极易内存泄漏。

正确做法:禁用栈分配 + 提供受控的堆创建接口

核心是两步:一是让编译器无法生成栈对象(通过删除默认构造函数和禁止拷贝/移动),二是只暴露静态工厂方法返回智能指针。析构函数必须是 public,否则 std::unique_ptr 析构时会报错 error: 'ClassName::~ClassName()' is private

常见错误现象:
- 声明 static std::unique_ptr create() 但析构函数私有 → 编译失败
- 只禁用构造函数却不删除拷贝/移动 → 对象仍可能被复制到栈上
- 工厂函数返回裸指针 → 用户忘记 delete 或重复 delete

  • 将默认构造函数、拷贝构造函数、移动构造函数、拷贝赋值、移动赋值全部声明为 delete
  • 析构函数保持 public,且通常应为 virtual(若类可能被继承)
  • 提供 static std::unique_ptr create(...) 工厂函数,内部用 newstd::make_unique
  • 若需自定义删除器(如对象需特殊释放逻辑),可重载 operator delete,但非必需
class HeapOnly {
public:
    static std::unique_ptr create(int x) {
        return std::make_unique(x);
    }
~HeapOnly() = default; // 必须 public!

private: explicit HeapOnly(int x) : value(x) {}

// 禁止所有栈相关操作
HeapOnly() = delete;
HeapOnly(const HeapOnly&) = delete;
HeapOnly(HeapOnly&&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
HeapOnly& operator=(HeapOnly&&) = delete;

int value;

};

替代方案:使用 placement new + 自定义内存池(高级场景)

当需要更精细控制(如对象必须分配在特定内存区域、避免全局堆碎片),可结合 private 构造函数 + static 内存池 + placement new。此时析构函数仍需 public,且必须显式调用(因不走默认 delete)。典型错误是忘记手动调用析构函数,或调用后未归还内存。

LangChain
LangChain

一个开源框架,用于构建基于大型语言模型(LLM)的应用程序。

下载

立即学习C++免费学习笔记(深入)”;

  • 内存池需线程安全(如用 std::mutex 保护分配/释放)
  • 工厂函数返回对象指针时,必须配套提供 destroy() 静态方法
  • 禁止用户直接调用 operator delete,否则破坏内存池一致性
  • 该方案复杂度高,仅适用于嵌入式、实时系统等有明确内存布局要求的场景

最容易被忽略的点:友元与继承的影响

如果类有友元函数或友元类,它们仍能访问私有构造函数和析构函数,可能绕过限制;若类被继承,子类的析构函数若未显式声明为 virtual,会导致基类析构不被调用。更隐蔽的问题是:C++17 后,某些编译器对 std::make_unique 的实现依赖于构造函数的可访问性,若构造函数是 private 且无恰当友元声明,std::make_unique 会编译失败。

解决方案:
- 若需支持 std::make_unique,将构造函数设为 private 并声明 friend std::unique_ptr std::make_unique(int&&);(不推荐,过于耦合)
- 更稳妥的是在工厂函数中直接用 new,避开 make_unique 的访问检查
- 所有继承体系中,基类析构函数必须为 virtualpublic

相关专题

更多
scripterror怎么解决
scripterror怎么解决

scripterror的解决办法有检查语法、文件路径、检查网络连接、浏览器兼容性、使用try-catch语句、使用开发者工具进行调试、更新浏览器和JavaScript库或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

187

2023.10.18

500error怎么解决
500error怎么解决

500error的解决办法有检查服务器日志、检查代码、检查服务器配置、更新软件版本、重新启动服务、调试代码和寻求帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

271

2023.10.25

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

315

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

537

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

52

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

197

2025.08.29

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1018

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

62

2025.10.17

Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

6

2026.01.15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 1.0万人学习

进程与SOCKET
进程与SOCKET

共6课时 | 0.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号