新用户注册入口 老用户登录入口

[转载]c++中两个类的头文件互相包含编译出错的解决办法

文章作者:转载 更新时间:2024-01-02 13:45:40 阅读数量:569
文章标签:编译错误循环包含前置声明指针使用CLayer类析构函数
本文摘要:本文针对C++编程中两个类互相包含头文件导致的编译问题进行探讨。通过实例分析,当“CLayer”类和“CSymbol”类相互依赖并直接包含对方头文件时,会出现循环包含错误。为解决此问题,利用预编译指令“#pragma once”以及类的前置声明来避免重复编译,并指出在类的实现文件中需包含实际头文件以访问完整类定义,特别是当涉及对象删除时,如调用析构函数。文章强调,在使用前置声明的情况下,由于编译器无法获取类的具体信息(大小、成员函数等),只能通过指针来安全操作未完全声明的类类型,以消除编译警告。
转载文章

本篇文章为转载内容。原文链接:https://blog.csdn.net/suxinpingtao51/article/details/37765457。

该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。

作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。

如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。

首先我们需要问一个问题是:为什么两个类不能互相包含头文件?

所谓互相包含头文件,我举一个例子:我实现了两个类:图层类CLayer和符号类CSymbol,它们的大致关系是图层里包含有符号,符号里定义一个相关图层指针,具体请参考如下代码(注:以下代码仅供说明问题,不作为类设计参考,所以不适宜以此讨论类的设计,编译环境为Microsoft Visual C++ 2005,,Windows XP + sp2,以下同):

  1. //Layer.h 
  2. // 图层类 
  3. #pragma once 
  4.  
  5. #include "Symbol.h" 
  6.  
  7. class CLayer 
  8. { 
  9. public: 
  10.     CLayer(void); 
  11.     virtual ~CLayer(void); 
  12.     void CreateNewSymbol(); 
  13.  
  14. private: 
  15.  
  16.     CSymbol*    m_pSymbol;  // 该图层相关的符号指针 
  17. }; 
  18.  
  19. // Symbol.h 
  20. // 符号类 
  21. #pragma once 
  22.  
  23. #include "Layer.h" 
  24.  
  25. class CSymbol 
  26. { 
  27. public: 
  28.     CSymbol(void); 
  29.     virtual ~CSymbol(void); 
  30.  
  31. public: 
  32.  
  33.     CLayer *m_pRelLayer; // 符号对应的相关图层 
  34. }; 
  35.  
  36. // TestUnix.cpp : 定义控制台应用程序的入口点。 
  37. // 
  38.  
  39. #include "stdafx.h" 
  40. #include "Layer.h" 
  41. #include "Symbol.h" 
  42.  
  43. void main( void ) 
  44. { 
  45.      CLayer MyLayer; 
  46.  
  47. }

现在开始编译,编译出错,现在让我们分析一下编译出错信息(我发现分析编译信息对加深程序的编译过程的理解非常有好处)。
首先我们明确:编译器在编译文件时,遇到#include "x.h"时,就打开x.h文件进行编译,这相当于把x.h文件的内容放在#include "x.h"处。
编译信息告诉我们:它是先编译TestUnix.cpp文件的,那么接着它应该编译stdafx.h,接着是Layer.h,如果编译Layer.h,那么会编译Symbol.h,但是编译Symbol.h又应该编译Layer.h啊,这岂不是陷入一个死循环?
呵呵,如果没有预编译指令,是会这样的,实际上在编译Symbol.h,再去编译Layer.h,Layer.h头上的那个#pragma once就会告诉编译器:老兄,这个你已经编译过了,就不要再浪费力气编译了!那么编译器得到这个信息就会不再编译Layer.h而转回到编译Symbol.h的余下内容。
当编译到CLayer *m_pRelLayer;这一行编译器就会迷惑了:CLayer是什么东西呢?我怎么没见过呢?那么它就得给出一条出错信息,告诉你CLayer没经定义就用了呢?
在TestUnix.cpp中#include "Layer.h"这句算是宣告编译结束(呵呵,简单一句弯弯绕绕不断),下面轮到#include "Symbol.h",由于预编译指令的阻挡,Symbol.h实际上没有得到编译,接着再去编译TestUnix.cpp的余下内容。
当然上面仅仅是我的一些推论,还没得到完全证实,不过我们可以稍微测试一下,假如在TestUnix.cpp将#include "Layer.h"和#include "Symbol.h"互换一下位置,那么会不会先提示CSymbol类没有定义呢?实际上是这样的。当然这个也不能完全证实我的推论。

照这样看,两个类的互相包含头文件肯定出错,那么如何解决这种情况呢?一种办法是在A类中包含B类的头文件,在B类中前置盛明A类,不过注意的是B类使用A类变量必须通过指针来进行,具体见拙文:类互相包含的办法。

为何不能前置声明只能通过指针来使用?通过分析这个实际上我们可以得出前置声明和包含头文件的区别。

我们把CLayer类的代码改动一下,再看下面的代码:

  1. // 图层类 
  2.  
  3. //Layer.h 
  4.  
  5. #pragma once 
  6.  
  7. //#include "Symbol.h" 
  8.  
  9. class CSymbol; 
  10.  
  11. class CLayer 
  12. { 
  13. public: 
  14.     CLayer(void); 
  15.     virtual ~CLayer(void); 
  16.  
  17. //    void SetSymbol(CSymbol *pNewSymbol); 
  18.     void CreateNewSymbol(); 
  19.  
  20. private: 
  21.  
  22.     CSymbol*    m_pSymbol; // 该图层相关的符号 
  23.  
  24. //    CSymbol m_Symbol; 
  25.  
  26. }; 
  27. // Layer.cpp 
  28.  
  29. #include "StdAfx.h" 
  30. #include "Layer.h" 
  31.  
  32. CLayer::CLayer(void) 
  33. { 
  34.     m_pSymbol = NULL; 
  35. } 
  36.  
  37. CLayer::~CLayer(void) 
  38. { 
  39.     if(m_pSymbol!=NULL) 
  40.     { 
  41.         delete m_pSymbol; 
  42.         m_pSymbol=NULL;         
  43.     } 
  44. } 
  45.  
  46. void CLayer::CreateNewSymbol() 
  47. { 
  48.      
  49. }

然后编译,出现一个编译警告:>f:\mytest\mytest\src\testunix\layer.cpp(16) : warning C4150: 删除指向不完整“CSymbol”类型的指针;没有调用析构函数
1> f:\mytest\mytest\src\testunix\layer.h(9) : 参见“CSymbol”的声明
看到这个警告,我想你一定悟到了什么。下面我说说我的结论:

类的前置声明和包含头文件的区别在于类的前置声明是告诉编译器有这种类型,但是它没有告诉编译器这种类型的大小、成员函数和数据成员,而包含头文件则是完全告诉了编译器这种类型到底是怎样的(包括大小和成员)。
这下我们也明白了为何前置声明只能使用指针来进行,因为指针大小在编译器是确定的。
上面正因为前置声明不能提供析构函数信息,所以编译器提醒我们:“CSymbol”类型的指针是没有调用析构函数。

如何解决这个问题呢?

在Layer.cpp加上#include "Symbol.h"就可以消除这个警告。

本篇文章为转载内容。原文链接:https://blog.csdn.net/suxinpingtao51/article/details/37765457。

该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。

作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。

如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。

相关阅读
文章标题:[转载][洛谷P1082]同余方程

更新时间:2023-02-18
[转载][洛谷P1082]同余方程
文章标题:[转载]webpack优化之HappyPack实战

更新时间:2023-08-07
[转载]webpack优化之HappyPack实战
文章标题:[转载]oracle 同时更新多表,在Oracle数据库中同时更新两张表的简单方法

更新时间:2023-09-10
[转载]oracle 同时更新多表,在Oracle数据库中同时更新两张表的简单方法
文章标题:[转载][Unity] 包括场景互动与射击要素的俯视角闯关游戏Demo

更新时间:2024-03-11
[转载][Unity] 包括场景互动与射击要素的俯视角闯关游戏Demo
文章标题:[转载]程序员也分三六九等?等级差异,一个看不起一个!

更新时间:2024-05-10
[转载]程序员也分三六九等?等级差异,一个看不起一个!
文章标题:[转载]海贼王 动漫 全集目录 分章节 精彩打斗剧集

更新时间:2024-01-12
[转载]海贼王 动漫 全集目录 分章节 精彩打斗剧集
名词解释
作为当前文章的名词解释,仅对当前文章有效。
预编译指令 `#pragma once`在C++编程中,预编译指令`#pragma once`是一种非标准但被广泛支持的机制,用于确保头文件在单个编译单元中只被包含一次,从而避免因循环包含头文件导致的重复定义错误。在文章给出的例子中,当编译器遇到`#pragma once`时,会检查当前头文件是否已被包含过,如果是,则跳过后续编译以防止死循环。
前置声明(Forward Declaration)在C++编程语境中,前置声明是指在类或函数的实际定义之前声明其存在的语法形式。例如,在文章中,通过“class CSymbol;”这一句,编译器知道存在名为CSymbol的类类型,但不包括其实现细节(如大小、成员函数等)。这种方式允许在不需要完整类信息的情况下使用该类的指针或引用,以解决两个类互相包含对方头文件的问题。
智能指针(Smart Pointer)在C++编程中,智能指针是一种对象,它存储指向动态分配内存区域的指针,并在适当的时候自动释放该内存,从而简化内存管理并减少资源泄漏的风险。虽然文章没有直接提及智能指针,但在讨论类间依赖和指针使用时,智能指针如std::shared_ptr和std::unique_ptr是实际项目开发中经常使用的工具,尤其在仅前置声明类的情况下,它们也能安全地管理和操作相关类类型的实例。
延伸阅读
作为当前文章的延伸阅读,仅对当前文章有效。
在深入理解C++编程中类互相包含头文件所引发的问题后,我们可以进一步探讨现代C++开发中的模块化设计和前置声明的实际应用。随着C++17标准引入的模块(Modules)特性,这一问题有了更为优雅且高效的解决方案。模块能够明确地划分代码边界,从根本上解决了循环包含和编译时间过长的问题,同时也优化了编译器对类型信息的处理。
另外,在实际项目开发中,诸如Google的开源库Abseil也采用了接口类与实现分离的设计模式,通过前置声明和PImpl(Pointer to Implementation)手法,不仅避免了头文件循环包含,还提升了编译速度并保护了实现细节。这种设计思路对于大型软件系统来说至关重要,尤其是在强调团队协作、模块解耦以及持续集成的现代开发环境中。
同时,对于类成员指针的使用,C++11标准引入的智能指针如std::shared_ptr和std::unique_ptr,不仅确保了资源的自动管理,减少了内存泄漏的风险,而且它们在仅前置声明类的情况下也能安全使用,从而强化了前置声明在解决此类问题时的作用。
综上所述,在面对类间相互依赖关系时,除了传统的前置声明方法外,当代C++开发者还可利用新标准提供的先进特性,如模块化设计和智能指针等,以更加高效和安全的方式来组织和构建复杂的程序结构。这些新的实践方式有助于提升代码质量,增强系统的可维护性和可扩展性,并符合现代软件工程的最佳实践。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
xargs -I{} command {} - 将标准输入传递给命令进行批量处理。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
js实用表单模糊搜索和自动提示插件 10-05 简单的jQuery响应式手风琴特效 01-27 发布站点前如何为站点质量做进一步优化,几个不能不知道的小工具 01-26 HessianRPC中IllegalArgumentException异常解析:方法签名与参数类型匹配在分布式系统中的实践误区与解决方案 01-16 AI助手的工作原理与限制:无法按特定要求撰写的原因及信息处理分析 12-27 Gallerybox-全屏响应式jQuery图片画廊插件 12-17 关于金融理财公司网站模板下载 11-01 SparkContext停止与未初始化错误排查:从初始化到集群通信与生命周期管理实践 09-22 jQuery和CSS3超酷3D拉窗帘式滚动导航特效 09-02 本次刷新还10个文章未展示,点击 更多查看。
简约蓝色农村电线线路安装网站模板 08-01 Tomcat性能瓶颈问题识别与解决:利用VisualVM和JProfiler分析工具进行代码优化与系统参数调整 07-31 图文经典商务外贸求职招聘企业网站模板 07-14 SeaTunnel中创建与应用自定义Transform插件:实现数据转换与业务逻辑处理,配置文件参数设置及插件打包发布 07-07 响应式精密光学仪器设备类企业前端CMS模板下载 06-12 vue口诀 04-23 宽屏蓝色海洋主题设计网站模板 04-21 美食自媒体博客类网页模板源码 04-14 公式计算 html 代码 04-01 [转载]C/C++劫持技术(函数劫持、dll注入、动态库注入、HOOK) 01-23 jQuery高仿真移动手机滑动侧边栏布局插件 01-21
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"