c++基础-头文件相互引用与循环依赖问题
2021/10/16 17:09:37
本文主要是介绍c++基础-头文件相互引用与循环依赖问题,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
本文主要分析C++头文件的相互引用,与类的相互依赖问题
1. C++头文件的相互引用
如果C++头文件相互引用,编译无法通过:
// A.cpp #include "A.h" int main() { return 0; } // A.h #include "B.h" // B.h #include "A.h"
尝试编译,报错
from A.h:1, from B.h:1, from A.h:1, from B.h:1, from A.h:1, ... ... from B.h:1, from A.h:1, from B.h:1, from A.cpp:1: B.h:1:15: error: #include nested too deeply #include "A.h"
这是由于预处理阶段,A.h与B.h,相互嵌套,导致头文件展开无限循环。
使用#ifndef或#pragma once
为了避免同一个头文件被包含(include)多次,C/C++中有两种宏实现方式:一种是#ifndef方式;另一种是#pragma once方式。
在能够支持这两种方式的编译器上,二者并没有太大的区别。但两者仍然有一些细微的区别
方式一:
#ifndef __SOMEFILE_H__ #define __SOMEFILE_H__ ... ... // 声明、定义语句 #endif
#ifndef的方式可以保证同一文件不会被包含多次,也能保证内容完全相同的两个文件不会被同时包含。需要注意不同文件中的__SOMEFILE_H__宏命名不能相同。
方式二:
#pragma once ... ... // 声明、定义语句
#pragma once由编译器保证同一个文件不会被包含多次,不能处理两个文件内容相同的情况,如果相同的文件在两个位置就会被处理两次。GCC3.4版本以前不支持#pragma once。
2. 类的循环依赖
#ifndef和#pragma once解决了头文件循环引用的问题,但是如果存在类的相互依赖,编译会出现新的问题:
// A.cpp #include "A.h" int main() { return 0; } // A.h #include "B.h" class A { B b; } // B.h #include "A.h" class B { A a; }
此时编译报错:
In file included from A.h:2:0, from A.cpp:1: B.h:5:5: error: ‘A’ does not name a type A a; ^
错误原因:
在A.h:2,处理语句#include “B.h”,进行头文件展开
在B.h:5进行的是在class B中声明一个A类型的成员变量,而此时class A还没有被声明
因此编译报错:‘A’ does not name a type
解决循环依赖的问题有两种方式:
1.使用前向声明(forward declaration)
2.设计层面避免循环引用
a.使用前向声明
C++的类可以进行前向声明,此例子中在B.h文件中对class A进行前向声明,就可以编译通过,如下:
// #include "A.h" class A; class B { A* a; };
由于前向声明而没有定义的类是不完整的,所以class A只能用于定义指针、引用、或者用于函数形参的指针和引用,不能用来定义对象,或访问类的成员。
这是因为确定class B空间占用的大小,而A还没有定义不能确定大小,A是确定的指针大小,
因此Class B中可以使用A定义成员变量。
前向声明的作用:
1.不需要include头文件,大量引入的头文件会导致编译变慢
2.可以解决两个类相互循环调用的情况
b.重新设计程序结构,避免循环依赖
良好的程序设计可以避免循环依赖,参考接口隔离原则和依赖倒置原则:
接口隔离原则:
客户端不应该依赖它不需要的接口
类间的依赖关系应该建立在最小的接口上
通俗来讲提供给每个模块单一的接口
依赖倒置原则:
上层模块不应该依赖于底层模块,它们都应该依赖于抽象
抽象不应该依赖于细节,细节应该依赖于抽象
在A依赖B,B依赖A的情况下,考虑为什么A类要内含B类的成员呢,那么可以抽象出一个接口,一个抽象类class IB,B是它的实现,class A和B都依赖于class IB,循环依赖便消除了。
编码示例:
// C++中,如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类 class IB { public: virtual int getVal() = 0; } class B : public IB { public: A a; int val = 2; int getVal(){ return val; } }; class A { public: IB *b; int getValOfB() { return b->getVal(); } }; int main() { IB *b = new B(); A a; a.b = b; cout << a.getValOfB() << endl; return 0; }
此时class A和class B依赖于IB,class B依赖于class A,循环依赖被打破,并且class A仍能以接口形式得到class B的数据。
这篇关于c++基础-头文件相互引用与循环依赖问题的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-15PingCAP 黄东旭参与 CCF 秀湖会议,共探开源教育未来
- 2024-05-13PingCAP 戴涛:构建面向未来的金融核心系统
- 2024-05-09flutter3.x_macos桌面os实战
- 2024-05-09Rust中的并发性:Sync 和 Send Traits
- 2024-05-08使用Ollama和OpenWebUI在CPU上玩转Meta Llama3-8B
- 2024-05-08完工标准(DoD)与验收条件(AC)究竟有什么不同?
- 2024-05-084万 star 的 NocoDB 在 sealos 上一键起,轻松把数据库编程智能表格
- 2024-05-08Mac 版Stable Diffusion WebUI的安装
- 2024-05-08解锁CodeGeeX智能问答中3项独有的隐藏技能
- 2024-05-08RAG算法优化+新增代码仓库支持,CodeGeeX的@repo功能效果提升