“不完全类型”指在C++中有声明但又没有定义的类型。

2023-11-18



用delete删除一个只有声明但无定义的类型的指针,是危险的。这通常导致无法调用析构函数(包括对象本身的析构函数、成员/基类的析构函数),从而泄露资源。
示例代码:
引用
class C;                // 在另一个cpp文件中定义
C* createC();           // 在另一个cpp文件中定义
int main() {
    C* p = createC();
    delete p;           // 资源泄露
}


初步分析:类型C没有被定义,所以无法找到析构函数的原型(虽然析构函数是没有返回类型、没有参数,但这些信息还不足以确定整个析构函数的原型。例如:析构函数是否是private?析构函数是否是virtual?这些信息现在并不明确)。另外,也无法知道这个类是否重载了operator delete,但是最后一句delete p却实实在在的通过编译了。

这样的结果很让人困惑,为什么在有如此多信息不明确的情况下,delete p仍然可以通过编译?其实也有一个稍微好理解一点的情况:
引用
class C;
C& createC();
int main() {
    C& rc = createC();
    C* p = &rc;         // 使用operator &
}

这里使用了C::operator&。按照C++语法,如果没有定义C::operator&,则编译器会为它自动产生一个;如果定义了C::operator&,则编译器不再会自动产生。由于此处类型C没有被定义,显然不可能知道是否定义了C::operator&,但是这段代码仍然可以通过编译。它的实际行为就好象没有定义C::operator&一样。也就是说,即使用户(在别的地方)定义了C::operator&,编译器也不会看到,但是这里必须使用了,因此它自动产生一个operator&。

回过头来看前面的delete p,也可以理解了。编译器没有看到C::~C(),也没有看到C::operator delete,那么就当作程序员没有定义这些内容。对于析构函数,本来编译器应该在析构函数调用之前先调用基类的析构函数和成员的析构函数,但是现在基类和成员都无法确定,因此只有不调用。对于operator delete,编译器没有看到它,因此也当它不存在。
所以最后的结果就是:只释放指针所指的内存,不调用析构函数,也不调用基类和成员的析构函数。换句话说,前面例子中的delete p,实际上已经变成了delete (void*)p。后面一种写法的危险性是显而易见的。

或许你认为这个情况很傻,几乎不会遇到。在设计上,通常会成对的提供接口,比如有了createC就应该有destroyC。不过如果设计疏忽,上面的代码又不会有任何编译错误(实验发现VC6会出现警告,但对于operator&则不会有任何警告),则最终会导致问题。
但是有的设计者可能会这样做:提供一个“std::auto_ptr<C> createC()”,创建一个对象,并且在不需要的时候销毁之。由于std::auto_ptr内部仍然是调用的delete,所以问题仍然存在,并且埋藏得更深。
用boost::shared_ptr的话,情况就会好一些。假设main函数在A文件,createC函数在B文件,则对于std::auto_ptr来说,析构时实际上是在A文件中调用delete,由于A文件中class C没有定义,所以出现问题。但对于boost::shared_ptr来说,在构造时就把delete作为“删除器”传入boost::shared_ptr内部,因此在析构时实际上是调用B文件中的delete,由于在B文件中需要实际的创建class C,所以B文件通常是必须包含class C的定义的,这里使用delete没有问题。
把class C的析构函数定义为private的话,很容易的看到,std::auto_ptr的版本仍然可以通过编译,但boost::shared_ptr的版本则出现错误了。

要知道“目前正在编译的文件中,是否存在某个类型的定义”,可以用sizeof。某些编译器对未定义的类型执行sizeof会得到零(?),VC系列对于未定义类型执行sizeof会得到编译错误。为了统一接口,可以用BOOST_STATIC_ASSERT(sizeof(C) >= 0);,或者在没有安装boost的时候直接写static const char arr[sizeof(C)];
利用boost::checked_delete(当然还有一个boost::checked_array_delete)代替delete,可以检查类型是否已经有定义。如果未定义则给出一个编译期错误,如果有定义则直接执行delete操作。

最奇怪的就是:在类没有定义的情况下,不是所有的operator都按照“编译器默认产生的动作”来运行,例如使用operator=就会出现编译错误。
引用
C* p = create();
*p = *p;         // 编译错误。不会自动生成operator =


小结:
对于一个有声明但未给出定义的“不完整类型”,使用它时需要特别小心。它的析构函数、operator delete、operator &等,即使在别的文件中给出定义,在本文件中由于没有定义,仍然会按照编译器默认的方式执行(最危险的地方就是忽略了自定义的析构函数)。
特别小心一些智能指针(例如std::auto_ptr)内部实际上也是使用delete,所以在使用时应该确保类定义可见。一定要避免这样的设计:class C; std::auto_ptr<C> create();
对于operator&,最好的做法就是不要重载它。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

“不完全类型”指在C++中有声明但又没有定义的类型。 的相关文章

随机推荐

  • 解决k8s集群环境内存不足导致容器被kill问题

    背景 最近线上环境上出现了一个问题 k8s集群环境Pod中的tomcat容器运行一段时间后直接被killd 但有时一切看起来正常 不能准确判断在什么时机出现被Killd问题 本文就此问题介绍了Linux内存不足原因以及为什么特定进程会被杀死
  • nRF24L01单芯片2.4GHz收发模块射频信道频率

    框架图 管脚图 操作模式配置 射频信道频率 RF通道频率决定了 nRF24L01 使用的通道中心 该通道在 1Mbps 时占用 1MHz 带宽 在 2Mbps 时占用 2MHz 带宽 nRF24L01 可以在 2 400GHz 到 2 52
  • poll()函数详解

    poll提供的功能与select类似 不过在处理流设备时 它能够提供额外的信息 include
  • 测试流程简述

    测试流程 整体流程如下 需求评审 功能需求 性能需求 接口需求 测试计划 测试用例 用例评审 测试环境搭建 平台 架构 web服务器 数据库 执行用例 缺陷记录 缺陷跟踪和回归测试 测试报告 测试计划 测试计划 描述了要进行的测试活动的范围
  • 第八站:JavaScript的数据类型、运算符、流程控制语句

    第八站 JavaScript的数据类型 运算符 流程控制语句 欢迎来到第八站 JavaScript的数据类型 运算符 流程控制语句 在这一站 我们将深入探索JavaScript中的核心概念 为你揭示这个语言的奇妙之处 准备好继续冒险了吗 让
  • linux安装datax

    1 创建文件夹 存放安装包 cd opt mkdir datax cd datax 2 下载安装包 wget http datax opensource oss cn hangzhou aliyuncs com datax tar gz 3
  • 流程引擎是什么?有什么作用?

    编者按 本文详细论述了流程引擎的概念 流程引擎的作用以及选型的要旨 并介绍了自主研发具有中国特色的流程引擎 关键词 流程引擎 集成性 数据分析 BPMN2 0规范 中国特色 流程审批 自主研发 流程引擎是什么 流程引擎 用来驱动业务按照设定
  • Python基础语法(函数式编程)

    目录 实例1 温度转换 实例2 蟒蛇绘制 模块1 turtle库 基本图形绘制 基本数据类型 1 整数 浮点数 复数 1 整数 2 浮点数 3 复数 4 数值运算操作符 实例3 天天向上的力量 2 字符串 模块2 time库 时间的基本处理
  • SpringCloud文件上传

    2 实现图片上传 刚才的新增实现中 我们并没有上传图片 接下来我们一起完成图片上传逻辑 文件的上传并不只是在品牌管理中有需求 以后的其它服务也可能需要 因此我们创建一个独立的微服务 专门处理各种上传 2 1 搭建项目 2 1 1 创建Spr
  • Android:播放UDP流例如udp://@239.0.0.3:8218

    成功实现播放udp github下载 求大佬们给个star GitHub YangWenlong71 udpplayer 基于ijk重新编译 未做删减几乎全能的安卓视频播放器 支持播放UDP https http 等 分割线 研究思路及结果
  • Django(17):Cookie 和 Session

    目录 Cookie 什么是Cookie Django使用Cookie Cookie使用示例 session 什么是session Django使用session Session使用示例 小结 HTTP协议本身是 无状态 的 在一次请求和下一
  • Flutter——头像上传功能,实现照片选择及裁剪

    使用两个开发库 image picker和image crop 前者用来拍照或者从相册选择照片 后者用来裁剪 结果均为File类型 裁剪完成后可以直接上传文件 先写到这儿 有时间上代码 更新 实现的功能是点击头像图片 弹出选择框 选择拍照或
  • C# 流程图完整功能,矩形,菱形圆,三角形,直线,折线,放大,滚动条,保存等等功能(附下载链接)

    C 流程图完整功能 矩形 菱形圆 三角形 直线 折线 放大 滚动条 保存等等功能 点我下载项目源码 流程图具体功能如下 连接时图形有线帽 部分动漫展示 public virtual void Draw Graphics gr canvas
  • 51虚拟安卓系统v1.1.0.6-安卓端的虚拟机(支持root,xposed框架)

    应用名称 51虚拟安卓系统 应用包名 com f1player 应用版本 1 1 0 6 应用大小 266 87M 下载地址 链接 https pan baidu com s 1N9YWIafoI575GfKvtHkd3A 提取码 qnr2
  • Ubuntu系统中如何进行屏幕截图

    前言 我的环境是双系统 ubuntu20 04 但应该无论是什么版本的ubuntu都可以实现 方法 1 快捷键截图 在设置里找到键盘快捷键 找到截图目录 就可以看到有关截图的快捷键 可以自己手动更改 单击选项即可 一般使用shift pri
  • 【C++编程技巧】根据字符串中的指定字符作为分界将字符串拆分

    在C 中可以用split 函数方便的实现字符串的拆分 在C 中没有类似的函数 用strtok函数进行完成字符串分割 原型 char strtok char str const char delim 功能 分解字符串为一组字符串 参数说明 s
  • JAVA中的JeeSite框架基本简介

    JAVA的主流框架是很多的 每一个框架都有它的适用项目和条件 所有JAVA程序员都熟悉的肯定是常用的四大框架 而JeeSite这个框架使用的人却不是很多 但是这个框架却有它的独到之处 稳定 高效 调用方便 这里对JeeSite做一个简单的介
  • kill掉僵尸进程的方法(kill -9 <PPID>)

    ps A ostat ppid pid cmd grep e Zz 先用以上bash命令找到僵尸进程 Z右边第一列为PPID 第二列为PID kill 9 PPID 即PID对应的父进程即可 kill 9
  • 从TP、FP、TN、FN到ROC曲线、miss rate、行人检测评估

    从TP FP TN FN到ROC曲线 miss rate 行人检测评估 update 2018年1月31日22 21 56 最初版本是基于行人检测Piotr Dollar大佬的论文和代码胡乱写的 难免有错 严谨的paper请参考 The R
  • “不完全类型”指在C++中有声明但又没有定义的类型。

    用delete删除一个只有声明但无定义的类型的指针 是危险的 这通常导致无法调用析构函数 包括对象本身的析构函数 成员 基类的析构函数 从而泄露资源 示例代码 引用 class C 在另一个cpp文件中定义 C createC 在另一个cp