C++知识分享:前置声明及其解析

Posted 一起学编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++知识分享:前置声明及其解析相关的知识,希望对你有一定的参考价值。

前置声明是C/C++开发中比较常用的技巧,主要用在三种情形:

变量/常量,例如extern int var1;;

函数,例如void foo();,注意类的成员函数无法单独做前置声明;

类,例如class Foo;,也可以前置声明模板类:template class<typename T1, int SIZE>Foo;。如果类包含在名字空间中,需在名字空间内做前置声明:namespace tlanyan class Foo;;,而不能这样:class tlanyan::Foo;。

前置声明作用

根据其用途,前置声明的主要作用为:

(1)避免重复定义变量;

(2)避免引入函数定义/声明文件,从而函数文件发生更改时不会重新编译依赖文件;

(3)解决循环依赖问题。

前两种用途好理解,第三种稍微复杂点,但却是前置声明最重要的用途。其解决类A包含类B,同时类B包含类A的依赖问题。循环依赖一般是设计层面的问题,可通过接口、引入辅助类等手段化解。前置声明也能解决,只是架构上稍微别扭。

不管A和B是否定义在同一个文件中,c++永远无法解决如下形式的循环依赖(后文解释原因):

前置声明解决该问题需要与指针配合,转换成另一种形式。要点如下:

至少将某类的变量类型转换成指针,例如A中将B转成B*;

类A中对B使用前置声明;

类A的定义文件中移除对类B文件的包含(做了包含保护则可忽略)。

使用前置声明后,以下是一种可行的解决形式(两个类均使用了前置声明):

深入前置声明

如果你有其他编程语言的经验,会发现c++有点怪异:Java/C#/Python/php等语言可以轻松做到循环引用,无需使用类似的前置声明技巧。这不禁让人思考:C++为何必须要用前置声明才能化解?

原因在于C++定义对象有两种方式:一种是A a形式,a即对象,调用成员变量或函数用.,对象在栈中分配;另一种是A* a,a是指针,调用成员变量或函数用->,其指向地址存储实际对象,对象在堆中分配。

分配对象需要知道具体的内存大小,但以下形式我们不能确定类A和类B对象的大小:

对于这个简单例子,你可以直观认为A和B占用同样的内存,例如1字节,但也可以是2字节,3字节等;根据内存对齐要求,一般是4字节,8字节等。无论哪种情况,编译器无法确定其对象占用内存,便会报错停止编译。所以你应该知道为什么C++永远不应该(不能)这样做了吧?

那为何前置声明加指针的组合能解决循环引用问题的呢?因为正常情况下,数据类型指针在同一机器的编译器里占同样的内存。指针一般是4或者8个字节,对应32和64位指针。用了指针,即使有循环引用,类的大小也能轻易的确定下来。这也是Java/C#/Python/PHP等可以轻松循环引用的原因:这些语言中,对象变量其实都是指针,也意味着对象变量都是引用传递。

如果不移除文件的相互包含,能否省去前置声明呢?答案是不能,原因如下:

1、C++按照一个个编译单元(translation unit)进行编译,如果两个文件互相包含且没有#pragma once等包含保护措施,则会出现递归包含,编译器报错;

2、如果两个头文件都有文件包含保护,编译A时会把B包含进来,但因为B包含了A,A中的包含保护生效,导致B文件内的内容实际未引入A,于是报B为未知符号的错误。

总的来说,不管是否移除对方的头文件,前置声明都是必须的。实践中为了避免文件变动时重新编译的耗费,移除不必要的头文件是一个好习惯。


那么今天的分享就到这里了,后续会更新更多精彩项目或者知识内容的,大家要好好学C语言C++哟~

写在最后:对于准备学习C/C++编程的小伙伴,如果你想更好的提升你的编程核心能力(内功)不妨从现在开始!

C语言C++编程学习交流圈子,QQ群:632264260点击进入】微信公众号:C语言编程学习基地

整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)

欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!

编程学习视频分享:

 

以上是关于C++知识分享:前置声明及其解析的主要内容,如果未能解决你的问题,请参考以下文章

C++基础语法梳理:虚函数及其相关知识点

IT好课专区《重学C++重构你的C++知识体系》百度云分享

2020 重学C++ 重构你的C++知识体系(完结)(百度网盘课程资源分享)

C++知识分享:C++函数修饰符总结

深度解析,抖音知识付费短视频内容制作流程,步骤技巧分享

C++知识分享:C++类特殊成员函数