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

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

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

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

前置声明作用

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

避免重复定义变量;

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

解决循环依赖问题。

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

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

// file: A.hpp

#include "B.hpp"

class A {

int id;

B b;

};

// file: B.hpp

#include "A.hpp"

class B {

...

A a;

};

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

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

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

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

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

// file: A.hpp

//3. 移除对B的包含(使用了#pragma once或者#ifndef B_HPP等保护措施则无必要)

// 2. 前置声明类B

class B;

class A {

int id;

// 1. 成员变量转换成指针

B* b;

};

// file: B.hpp

// 3. 移除对A的包含(有包含保护则非必要)

// 2. 前置声明类A

class B {

...

// 1. 成员变量转换成指针

A* a;

};

深入前置声明

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

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

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

class A {

B b;

};

class B {

A a;

};

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

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

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

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

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

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

以上就是详解C++ 前置声明的详细内容,更多关于C++ 前置声明的资料请关注我们其它相关文章!

时间: 2020-09-08

c++ using 前置声明_详解C++ 前置声明相关推荐

  1. swift. 扩展类添加属性_swift中的声明关键字详解

    原起 学习swift,swift中的关键字当然要了解清楚了,最近在网上看到了关于声明关键字的文章,整理记录一下. 关键字是类似于标识符的保留字符序列,除非用重音符号(`)将其括起来,否则不能用作标识符 ...

  2. 大脑构造图与功能解析_大脑的结构和功能分区_详解人脑构造与功能

    大脑的结构和功能分区 _ 详解人脑构造与功能 学习,可以开阔人的大脑 ; 学习,可以使人的大脑拥有更多的知识,人的大脑和肢 体一样,多用则灵,不用则废.那么下面学习啦小编给大家分享一些大脑的结构和功 ...

  3. smali语言详解之类的声明

    smali语言详解之类的声明 一.smali语言的类声明格式 .class 权限修饰关键字 类的全包名路径 二.示例 2.1.一般类(无继承和接口实现) java代码如下 public class T ...

  4. c++ 智能指针_详解 C++ 11 中的智能指针

    C/C++ 语言最为人所诟病的特性之一就是存在内存泄露问题,因此后来的大多数语言都提供了内置内存分配与释放功能,有的甚至干脆对语言的使用者屏蔽了内存指针这一概念.这里不置贬褒,手动分配内存与手动释放内 ...

  5. python变量定义大全_详解python变量与数据类型

    这篇文章我们学习 Python 变量与数据类型 变量 变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念,变量可以通过变量名访问.在 Python 中 变量命名规定,必须是大小写英文,数字 ...

  6. python3.4和3.6的区别_详解Python3.6正式版新特性

    按照Python官网上的计划,Python3.6正式版期望在2016-12-16号发布,也就是这周五.从去年的5月份开始,Python3.6版本就已经动手开发了,期间也断断续续的发布了4个Alpha版 ...

  7. dumpbin发现没有入口函数_详解VS2019 dumpbin查看DLL的导出函数

    之前有人问过ViewDLL这些软件在哪下载.实际上使用VS就可以查看DLL的导出接口. 1.先打开VS内部的Power Shell 2.打开以后应该如下图所示,首先输入dumpbin查看下用法: 3. ...

  8. java 变量与常量_详解Java变量与常量

    一.常量 用final修饰(也称最终变量) 常量在声明时必须赋初值,赋值后不能再修改值 常量名通常用全大写字母表示 声明时需要添加final或static final类型修饰符,例如: private ...

  9. python 线程等待_详解python多线程之间的同步(一)

    引言: 线程之间经常需要协同工作,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直到该线程完成对数据的操作.这些技术包括临界区(Critical Section),互斥量(Mute ...

最新文章

  1. swing LayoutManager 和多态
  2. oracle中的listener.ora和tnsnames.ora
  3. 12道Python基础字符编码数据类型练习题
  4. vim 命令_Vim 操作命令手册
  5. 一个go1.9.x 编译器内联引起的栈信息错乱的问题分析
  6. python计算夏令时的具体日期,python time.time()和“夏令时”;
  7. 蓝桥杯“基础练习:查找整数
  8. Java 容器之 Connection栈队列及一些常用
  9. 3796.凑平方-AcWing题库
  10. 即拿即用-Android单线程断点下载
  11. PLC1200配方功能使用---配方功能快速入门
  12. 实话实说!猿如意大测评!助力coding!
  13. 前端系列教程之推荐(实用网站 Can I use)
  14. 【PostgreSQL逻辑复制数据同步到kafka】
  15. 广东省教育局 计算机,广东省教育厅关于公布第十七届广东省中小学电脑制作活动获奖结果的通知...
  16. 60行JavaScript代码写俄罗斯方块
  17. 【无标题】解决“该项目不在请确认该项目位置,然后重试” 文件无法删除问题
  18. 2020 中科院 CVPR : Context-Aware Attention Network for Image-Text Retrieval
  19. 纵向冗余校验计算方法_常见校验算法
  20. jdbc连接oracle连接重置问题

热门文章

  1. 2020山东省计算机专科学校排名,2021山东专科学校排名 最好的高职院校排行榜
  2. 大唐豪侠服务器列表文件格式错误,[动态] 大唐豪侠1.2.4版本更新公告-大唐豪侠-东北游戏网...
  3. win7 更改IP 脚本(自动获取和手动设置多个IP),将里面内容拷贝到记事本另存为set_win7_IP.bat
  4. java map 实例_java中map集合嵌套形式简单示例
  5. Hibernate中的sql的所有的查询
  6. Linux中安装oracle
  7. Springboot+Mysql物流快递在线寄查快递系统
  8. 基于JAVA+SpringMVC+MYSQL的医院皮试管理系统
  9. You have provided a value for the LANGUAGE_CODE setting that is not in the LANGUAGES setting
  10. linux删除具有指定内容的文件,Linux bash删除文件中含“指定内容”的行功能示例...