“不完全类型”指在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++中有声明但又没有定义的类型。 的相关文章

  • 仅使用扩展方法在 Linq 中进行漂亮、干净的交叉连接 [重复]

    这个问题在这里已经有答案了 可能的重复 使用扩展方法表示的嵌套 from LINQ 查询 https stackoverflow com questions 9115675 nested from linq query expressed
  • 如何使用 ILoggerFactory 记录 Polly 的重试

    或者 如何从静态方法记录 From https github com App vNext Polly https github com App vNext Polly你有这样的例子 其中记录器神奇地可用 Policy Timeout 30
  • MySql 最后插入 ID,连接器 .net

    我正在使用 MySql Connector net 我需要获取最后一个查询生成的插入 id 现在 我假设返回值是MySqlHelper ExecuteNonQuery应该是最后一个插入id 但它只返回1 我正在使用的代码是 int inse
  • 使用预编译头减少 clang 编译时间

    我正在开发一个数据库项目 该项目将查询 以某种高级语言表示 编译为 C 代码 这段代码由数据库编译并执行 那部分工作得很好 现在 我正在尝试减少 C 查询代码的编译时间 我想知道是否可以使用预编译头来提高性能 该查询被转换为一个名为 Que
  • 有没有办法将 boost::json::serializer 切换为美化输出?

    Using boost json serializer如中的示例所示文档 快速查看 http vinniefalco github io doc json json usage quick look html以紧凑格式保存 json tre
  • 如何使用boost库读取和写入.ini文件[重复]

    这个问题在这里已经有答案了 如何使用boost库读取和写入 或修改 ini文件 With Boost PropertyTree您可以读取并更新树 然后写入文件 请参阅load and save功能 看一下如何访问属性树中的数据 http w
  • Qt QML 数据模型似乎不适用于 C++

    我一直在使用中的示例http doc qt digia com 4 7 qdeclarativemodels html http doc qt digia com 4 7 qdeclarativemodels html这是 QML 声明性数
  • C++ 错误:从“char”到“const char*”的转换无效

    我对 C 完全陌生 我创建了这个函数 bool guessWord string compWord cout lt lt Guess a letter string userLetter cin gt gt userLetter for u
  • 使用 INotifyPropertyChanged

    有人可以解释一下为什么在 wpf 中使用绑定时需要使用 INotifyPropertyChanged 的 实现吗 我可以在不实现此接口的情况下绑定属性吗 例如我有代码 public class StudentData INotifyProp
  • Visual Studio Code 调试默认 ASP.NET Core MVC WebApp:不起作用

    我正在使用 Manjaro linux 并尝试调试默认的 ASP NET Core MVC 项目 但调试停止 没有任何错误 我创建了该项目 dotnet new mvc in a Meow文件夹 没什么特别的 然后添加了新的配置 NET C
  • 使用 QGraphicsScene 实现流畅的动画

    我希望我的问题并不总是同样的问题 我有一个 QGraphicsScene 它的项目是一些 QGraphicsPixmap 我用一个计时器来移动它们 每秒 SetX 10 我设置 10是因为窗口大100 使用这个解决方案我的动画不流畅 我想我
  • 数组与映射的性能

    我必须循环一个大数组中的元素子集 其中每个元素都指向另一个元素 问题来自于检测大图中的连接组件 我的算法如下 1 考虑第一个元素 2 将下一个元素视为前一个元素所指向的元素 3 循环直到没有发现新元素 4 考虑1 3中尚未考虑的下一个元素
  • 非静态类中的静态方法和静态类中的静态方法有什么区别?

    我有两个班级A级和B级 static class ClassA static string SomeMethod return I am a Static Method class ClassB static string SomeMeth
  • 参数数量在编译时确定的 Lambda 函数

    我想声明一个带有 N 个参数的 lambda 函数 其中 N 是模板参数 就像是 template
  • 为什么调试器只显示数组指针中的一个元素?

    首先 我知道new是执行此操作的 C 方法 我只是表明有不止一种方法可以重现此错误 而且两种方法都令人难以置信的令人沮丧 我有两种形式的源文件 我正在尝试调试另一个编程作业 但我并没有寻求帮助 基本上 我正在尝试重新实施set作为一个类 具
  • OpenGL 计算着色器调用

    我有一个与新计算着色器相关的问题 我目前正在研究粒子系统 我将所有粒子存储在着色器存储缓冲区中 以便在计算着色器中访问它们 然后我派遣一个一维工作组 define WORK GROUP SIZE 128 shaderManager gt u
  • 获取会议组织者邮件地址 EWS API

    我想使用 EWS API 获取会议组织者的邮件地址 目前 我刚刚获得约会项目的一些属性 我听说你可以设置你想要获取哪些属性 我的代码看起来像这样 CalendarView cview new CalendarView start end c
  • 如何在没有 Visual Studio 的情况下将新文件添加到 .csproj 文件

    如何添加新文件到 csproj从命令提示符 我认为没有任何工具可以响应命令行上的 add project 命令来执行此操作 但我认为您可以幸运地创建一个程序 脚本来直接操作 csproj 文件的 XML 内容 csproj 文件的结构如下所
  • C# amo 获取角色完整

    我正在开发一个 SSAS 项目 其中除其他事项外 我需要获取 C 中表格多维数据集的完整用户列表 目前我让它以这样的方式工作 我可以获得角色 但数据不完整 当我调用 Server Database Roles 为了便于阅读而简化 属性并枚举
  • 类模板的 C++ 静态成员 - 链接器警告“多重定义”[重复]

    这个问题在这里已经有答案了 假设出于某种原因 我想要一个类模板 MyTemp 和一些静态数据成员 smDummyVar Mytemp h ifndef MY TEMP H define MY TEMP H template

随机推荐

  • 解决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