C++前置声明用法

2023-10-31

前置声明的目的是避免在某个.h文件中include其他头文件,取而代之的是用class/struct 声明;
类的前置声明就是告诉编译器有这么一个类,它的名字是XXX,甚至不需要知道它具有哪些成员;

注意,这里只是声明类,没有分配空间,实例化成对象的时候,编译器才会分配内存;

前置声明的好处:

  1. 算法封装后的接口类头文件中,可能会使用到其他头文件或第三方库中定义的数据类型,为了减少头文件之间的相互依赖(从而避免算法调用层除接口头文件之外,还要依赖更多的头文件,甚至还要配置第三方库的开发环境),可以使用前置声明;
  2. 当两个头文件交叉引用时(例如A.h中包含了B.hB.h中又包含了A.h),无法通过编译,必须使用前置声明;
  3. 节省编译时间;修改某个头文件后,依赖该头文件的其他头文件和源程序都要重新编译,而前置声明隐藏了部分依赖关系,从而减少不必要的编译工作;

1 一般类的前置声明

举例:
首先在Person.h中定义一个CPerson的类型,如下:

// Person.h
#pragma once
#include <iostream>
class CPerson
{
public:
    CPerson():miAge(0) {};
    ~CPerson() {};
    
    void PrintAge() 
    {
        std::cout << miAge << std::endl;
    };

    int miAge;		// 目前只有一个成员变量
};

现在要开发一个新的类CEarthCEarth中有个成员变量是CPerson类型,一般情况下的做法如下:
Earth.h中定义一个CEarth的类型,并包含Person.h文件,如下:

// Earth.h
#pragma once
#include "Person.h"

class CEarth
{
public:
	CEarth();
	~CEarth();

	void Run();

	CPerson mPerson;	// 有个CPerson类型的成员变量,因此include了"Person.h"
};

实现如下:

// Earth.cpp
#include "Earth.h"

CEarth::CEarth() {}
CEarth::~CEarth() {}
void CEarth::Run()
{
	mPerson.miAge = 18;
	mPerson.PrintAge();
}

如此带来的影响有:

  1. 如果别人要使用CEarth,不仅要给他提供Earth.h文件,还要提供Person.h文件,如果Person.h依赖了其他头文件,也要提供,如此递归下去,一方面别人调用的时候需要依赖更多的文件(如果涉及到第三方库还要配置开发环境),另一方面也影响底层算法的私密性;
  2. 如果类型Person发生了改变(例如添加属性“性别”),Person.h必然发生改变,由于Earth.h中包含了Person.h,这就意味着Earth.h也发生了改变,那么那些包含Earth.h的文件都得重新编译,而如果Earth.h中不包含Person.h,这就可以减少不必要的编译时间,在大型项目工程中影响比较大;

CEarth的定义和实现改成前置声明的写法,如下:

// Earth.h
#pragma once
//#include "Person.h"

class CPerson;	// 前置声明,告诉编译器有个类名字叫CPerson
class CEarth
{
public:
	CEarth();
	~CEarth();

	void Run();

	CPerson* mPerson1;	// 只能使用指针或引用
};

Earth.h中引入类的前置声明,而不用包含Person.h,告诉编译器有个类叫CPersonCPerson中有哪些成员在Earth.h中不需要知道;
由于类的前置声明不知道类中具体的成员,因此也就不能直接使用类型来定义类的对象(因为实例化对象时需要给对象分配内存,既然都不知道类中有几个成员,编译器也就无法分配内存),自然也就不能调用对象中的方法,因此CEarth中的成员变量只能使用Person的指针或引用,因为指针或者引用类型变量的大小是确定的(4或8字节);

这也正是为什么使用前置声明后,不管CPerson自身的定义会发生什么样的改变,CEarth也不会发生改变,因为CEarth中跟CPerson类型相关的成员变量始终只占一个指针大小的字节;

// Earth.cpp
#include "Earth.h"
#include "Person.h"

CEarth::CEarth() :mPerson1(nullptr) {}
CEarth::~CEarth() {}
void CEarth::Run()
{
	mPerson1 = new CPerson;
	mPerson1->miAge = 18;
	mPerson1->PrintAge();
	delete mPerson1;
	mPerson1 = nullptr;
}

Earth.cpp中需要包含CPerson.h文件,因为Earth.cpp中通常是类成员函数的实现部分,既然要用到CPerson类型,肯定是要用CPerson实例化对象,并使用CPerson对象中的成员变量和方法(否则CEarth中就没必要使用CPerson);
因此,不管是在.h还是.cpp文件中,判断能否使用CPerson类的前置声明代替包含Person.h文件的关键是,看该文件中是否一定要用到CPerson类的实例(一般来说,就是要通过实例访问CPerson类中的成员变量或方法),通常我们在封装一个类时(如CEarth),会拆成.h.cpp两个文件,.h文件用来进行类声明,不需要用到CPerson类的实例,可以使用前置声明,不用包含Person.h,而.cpp文件用来进行类方法实现,需要用到CPerson类的实例,必须包含Person.h

如果封装的类将声明和实现都写在.h文件中,那也不能使用前置声明,必须包含Person.h

另外,如前所述,使用CPerson类的前置声明后,CEarth中的成员变量只能使用CPerson类的指针或引用,但如果CPerson只是用作CEarth成员函数的参数或返回值,或只是作为标准STL模板容器的参数类型,指针或引用不是必须的,如下:

// Earth.h
#pragma once
//#include "Person.h"
#include <vector>

class CPerson;	// 前置声明,告诉编译器有个类名字叫CPerson
class CEarth
{
public:
	CEarth();
	~CEarth();

	void Run();

	CPerson GetPerson1(CPerson a);
	CPerson GetPerson2(CPerson& a);

	CPerson* mPerson1;	// 只能使用指针或引用
	std::vector<CPerson> mvecPerson;
	std::vector<CPerson*> mvecpPerson;
};

使用CPerson类的前置声明后(不包含Person.h文件),判断是否一定要使用指针或者引用的关键是,CPerson修饰的变量是否会改变CEarth实例化后的大小,成员函数或标准STL模板容器都不会改变CEarth的大小,因此,可以直接使用CPerson,而成员变量会改变CEarth的大小,因此,必须使用指针或引用(使用指针或引用后,成员变量的大小就始终只占1个指针大小的内存);

标准STL模板容器创建的对象,其内存大小与模板参数类型无关,也不会随着容器中的元素数量的改变而改变,此处不展开;

2 模板类及其实例的前置声明

OpenCV库中的Point2f类型为例,定义如下:

// opencv2/core/types.hpp
namespace cv
{
template<typename _Tp> class Point_
{
public:
	// ...
}

typedef Point_<float> Point2f;
}

也就是说,OpenCV中,Point2f是类模板Point_参数类型为float的实例,别名为Point2f
前置声明语法如下:

// Data.h
/*
* brief: 前置声明
*/
namespace cv
{
	template<typename T>
	class Point_;
	typedef Point_<float> Point2f;
}

class CData
{
publicCData() {};
	~CData() {};

private:
	std::vector<cv::Point2f> mvecPts2f;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++前置声明用法 的相关文章

随机推荐

  • llinux 写/etc/passwd文件添加用户

    llinux 写 etc passwd文件添加用户 openssl passd加密 前言 在做vulnhub靶机 AI web 1 0的时候 getshell用户具有 etc passwd写权限 etc shadow无读写权限 傻子将 et
  • [华为OJ--C++]087-在字符串中找出连续最长的数字串

    题目描述 在字符串中找出连续最长的数字串 如果是输入字符串中连续最长的数字串长度为0 则只输出数字0 如果输入的字串中最长的数字字串只有一组 那么输出这个数字字串再加上数字字串长度 用逗号隔开 如果输入字串中有多个相同长度的数字字串 那么依
  • 【深入理解计算机系统】第二章重点汇总

    2 1 信息的存储 十六进制转二进制 将十六进制的每一位转换成一个4位的二进制 即 0123456789 A B C D E
  • Android开发之获取网页源码

    在有关网络通讯方面的开发应用中有的时候 需要得到网页的源代码 然后对源代码进行一些处理 现在考虑最简单的例子 获取百度首页的源码 界面设计如下 图 1 基本功能就是点击 显示 按钮后将百度首页的源码显示在下方 首先 这里我们用到了访问网络的
  • 3dmax不能撤销

    3dmax是设计常用的软件 它既可以用来做图 也可以用来编程 但是现在出现很多的病毒 影响了3dmax的使用 例如有时候在使用3dmax做图的时候 会出现场景不能撤销的情况 这个时候就需要注意了 这个一般是感染了3dmax的ALC病毒导致的
  • JavaWeb --- JavaScript

    一 JavaScript介绍 JavaScript语言诞生主要是完成页面的数据验证 因此它运行在客户端 需要运行浏览器来解析执行JavaScript代码 JS是Netcape网景公司的产品 最早取名为LiveScript 为了吸引更多jav
  • IDEA如何执行maven命令进行打包编译及常用命令

    前提条件 maven配置环境变量 在保证环境变量配置没问题的情况下执行过程出现mvn不是内部命令类似的错误 建议重启编译器或者命令窗口 执行maven命令 方式一 在IDEA主界面左下角找到 Terminal 点击进入 直接输入想执行的命令
  • 出现 No services need to be restarted. No containers need to be restarted. No user sessions are 解决方法

    目录 前言 1 问题所示 2 解决方法 前言 此提示严重 如果有意关闭 可继续往下看 1 问题所示 当使用apt安装东西的时候 底下会出现如下问题 Scanning linux images Running kernel seems to
  • VScode运行C语言,qsort测试例子

    好久没用 刚好需要测试一下排序 顺便把VSCode的配置也记录一下 准备 1 安装编译环境 我用的mingw64 官网https sourceforge net projects mingw w64 files 记得安装好配置环境变量 或者
  • com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException:

    分析 出现这种异常的原因是 MySQL服务器默认的 wait timeout 是8小时 也就是说一个connection空间超过8个小时 mysql将自动断开该connection 这就是问题所在 在连接池中的connection如果空闲超
  • 富士施乐2022网络扫描设置_富士施乐sc2020网络扫描怎么设置?

    1 在计算机客户端添加一个命名为SMB 命名随意 的共享文件夹 这个文件夹是用来存储局域网网络扫描的文件 使用简单的共享方式设置文件夹属性 勾选 在网络上共享这个文件夹 和 允许网络用户更改我的文件 2 在已经安装好多功能办公设备的计算机客
  • undionly.kpxe php,VLOG

    经过研究 终于可以将ESXI的系统通过IPXE网络启动安装到无盘的软路由或者PC上了 当然也可以通过这种方法安装window linux等等其他的系统 一 编译IPXE增加功能与自定义脚本 一 iPXE 概要 按iPXE 官网的介绍是这样的
  • Java提高篇(二七)-----TreeMap

    原文出自 http cmsblogs com p 1013 尊重作者的成果 转载请注明出处 个人站点 http cmsblogs com TreeMap的实现是红黑树算法的实现 所以要了解TreeMap就必须对红黑树有一定的了解 其实这篇博
  • IOS-Xcode Compile flags

    flag 功能 fno objc arc 该文件不启用ARC fo objc arc 该文件启用ARC w 去除警告
  • 【google版efficientdet】官方版efficientdet训练自己的数据集,终于训练成功了

    看全网还没有一篇攻略 本文是第一个 有心人当点赞下 有问题可以下方留言 互相交流 如转载请注明出处 不枉解决各种各样的bug 环境 v100 cuda10 1 tensorflow2 1 0 python3 7 7 只保证这个版本是可行的
  • NUC980开源项目5-安装repo

    上面是我的微信和QQ群 欢迎新朋友的加入 项目码云地址 国内下载速度快 https gitee com jun626 nuc980 open source project 项目github地址 https github com Jun117
  • Pandas 报错 TypeError: ‘Series‘ objects are mutable, thus they cannot be hashed

    一 需求 根据原始 CSV 文件的列 A 的值 添加一列 B 二 尝试 1 1 将 A 列与 B 列对应的值写入字典 dict A 列为 key B 列为 value 2 将 CSV 文件处理为 DataFrame 3 import pan
  • Python入门02:详细来了解一下Requests库

    那个叫做 Urllib 的库让我们的 python 假装是浏览器 接下来我们要来玩一个新的库 这个库的名称叫做 Requests 这个库比 urllib 可是要牛逼一丢丢的 毕竟 Requests 是在 urllib 的基础上搞出来的 通过
  • 个人笔记随记——在CSDN写随记原因,部分是为了自己复习,查看。

    在CSDN写随记原因 部分是为了自己复习 查看 部分原因是用来分享经验 大家共同进步 之前我的几个电脑里面有个自己的个人数据库 所以笔记都在那里记录 因为现在除了码字 经常不携带电脑 导致笔记不能随时观看 所以现在即在CSDN开了个人博客
  • C++前置声明用法

    前置声明的目的是避免在某个 h文件中include其他头文件 取而代之的是用class struct 声明 类的前置声明就是告诉编译器有这么一个类 它的名字是XXX 甚至不需要知道它具有哪些成员 注意 这里只是声明类 没有分配空间 实例化成