C++前置声明用法

前置声明的目的是避免在某个.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;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值