C++ 前置声明 和 包含头文件 如何选择
假设有一个Date类
Date.h
class Date {
private:
int year, month, day;
};
如果有个Task类的定义要用到Date类,有两种写法
其一
Task1.h
class Date;
class Task1 {
public:
Date getData();
};
其二
Task2.h
#include "Date.h"
class Task2 {
public:
Date getData();
};
一个采用前置声明,一个采用#include<Date.h>加入了Date的定义。两种方法都能通过编译。但是 Task1.h 这种写法更好。如果Date.h 的 private 成员变量改变,比如变成 double year, month, day; ,Task1.h 不需要重新编译,而 Task2.h 就要重新编译,更糟的是如果 Task2.h 还与其他很多头文件有依赖关系,就会引发一连串的重新编译,花费极大的时间。可是事实上改变一下写法就可以省去很多功夫。
所以能用前置声明代替#include 的时候,尽量用前置声明
有些情况不能用前置声明代替#include
比如Task1.h改成
class Date;
class Task1 {
public:
Date d;
};
会编译错误,因为Date d定义了一个Date类型变量,编译器为d分配内存空间的时候必须知道d的大小,必须包含定义Date类的Date.h文件。
这是可以采用指针来代替
class Date;
class Task1 {
public:
Date *d;
};
指针的大小是固定的。在32位机上是4字节,64位机上是8字节。这时编译Task1的时候不需要Date的大小,所以和Date的定义无关。
何时可以用前置声明代替#include
在这里,我自己总结了可以使用前置声明来取代包括头文件的各种情况和给出一些示例代码。
首先,我们为什么要包括头文件?问题的回答很简单,通常是我们需要获得某个类型的定义(definition)。那么接下来的问题就是,在什么情况下我们才需要类型的定义,在什么情况下我们只需要声明就足够了?问题的回答是当我们需要知道这个类型的大小或者需要知道它的函数签名的时候,我们就需要获得它的定义。
假设我们有类型A和类型C,在哪些情况下在A需要C的定义:
- A继承至C
- A有一个类型为C的成员变量
- A有一个类型为C的指针的成员变量
- A有一个类型为C的引用的成员变量
- A有一个类型为std::list<C>的成员变量
- A有一个函数,它的签名中参数和返回值都是类型C
- A有一个函数,它的签名中参数和返回值都是类型C,它调用了C的某个函数,代码在头文件中
1,没有任何办法,必须要获得C的定义,因为我们必须要知道C的成员变量,成员函数。
2,需要C的定义,因为我们要知道C的大小来确定A的大小,但是可以使用Pimpl惯用法来改善这一点,详情请
看Hurb的Exceptional C++。
3,4,不需要,前置声明就可以了,其实3和4是一样的,引用在物理上也是一个指针,它的大小根据平台不同,可能是32位也可能是64位,反正我们不需要知道C的定义就可以确定这个成员变量的大小。
5,不需要,有可能老式的编译器需要。标准库里面的容器像list, vector,map,
在包括一个list<C>,vector<C>,map<C, C>类型的成员变量的时候,都不需要C的定义。因为它们内部其实也是使用C的指针作为成员变量,它们的大小一开始就是固定的了,不会根据模版参数的不同而改变。
6,不需要,只要我们没有使用到C。
7,需要,我们需要知道调用函数的签名。
上述例子可以说明
如果使用object reference 或 object point 可以完成任务,就不要用object
这样可以尽最大可能避免#include
#include "B.h"
class A
{
public:
A(void);
virtual ~A(void);
private:
B b;
};
#include "A.h"
class B
{
private:
A a;
public:
B(void);
~B(void);
};
一编译,就出现了一个互包含的问题了,A中有B类型的成员变量所以需要include<b.h>,而B中又有A类型的成员变量也需要include<a.h>,这就导致了循环include,编译是肯定通过不了的!
#include "B.h"
class B;
class A
{
public:
A(void);
virtual ~A(void);
private:
B b;
};
#include "A.h"
class B
{
private:
A a;
public:
B(void);
~B(void);
};
class CBed; // 盖房子时:现在先不买,肯定要买床的
class CHouse
{
CBed& bed; // 我先给床留个位置
// CBed bed; // 编译出错
public:
CHouse(void);
CHouse(CBed& bedTmp);
virtual ~CHouse(void);
void GoToBed();
};
#include "Bed.h"
#include "House.h" // 等房子开始装修了,要买床了
CHouse::CHouse(void)
: bed(*new CBed())
{
CBed* bedTmp = new CBed(); // 把床放进房子
bed = *bedTmp;
}
CHouse::CHouse(CBed& bedTmp)
: bed(bedTmp)
{
}
CHouse::~CHouse(void)
{
delete &bed;
}
void CHouse::GoToBed()
{
bed.Sleep();
}
C++ 前置声明 和 包含头文件 如何选择的更多相关文章
- ZT 头文件包含其实是一想很烦琐的工作 第一个原则应该是,如果可以不包含头文件
当出现访问类的函数或者需要确定类大小的时候,才需要用头文件(使用其类定义) http://blog.csdn.net/clever101/article/details/4751717 看到这个 ...
- C++中#include包含头文件带 .h 和不带 .h 的区别
C++中#include包含头文件带 .h 和不带 .h 的区别? 如 #include <iostream> 和 #include <iostream.h> 包含的东西有哪些 ...
- 在.h和.cpp中包含头文件的区别
1.在.h中包含头文件,是为了声明一系列这个头文件的变量等,可能会产生重复包含的问题: 2.在.cpp中包含头文件只是为了实现这个头文件或者使用其中的方法,不会有重复包含的问题,所以尽量在源文件中包含 ...
- include包含头文件的语句中,双引号和尖括号的区别是什么?
include包含头文件的语句中,双引号和尖括号的区别是什么? #include <> 格式:引用标准库头文件,编译器从标准库目录开始搜索 尖括号表示只在系统默认目录或者括号内的路径查找 ...
- C/C++不同文件夹下包含头文件的方法及#include的使用
转自:http://blog.sina.com.cn/s/blog_6e0693f70100so42.html 本文主要介绍了如何不同文件夹下使用预处理器指示符#include. 假设我们有如下一个工 ...
- include包含头文件的语句中,双引号和尖括号的区别
include包含头文件的语句中,双引号和尖括号的区别 #include <>格式:引用标准库头文件,编译器从标准库目录开始搜索 #incluce ""格式:引用非 ...
- c++包含头文件好还是重新定义好
A.h struct A { int a; int b; }; B.cpp 在B.cpp里面用到这个结构体 有两种方法 .自己定义一个一模一样的结构体 struct A { }; .包含A.h头文件 ...
- C++包含头文件时尖括号和双引号区别
原文链接:http://c.biancheng.net/cpp/biancheng/view/66.html 如果你还看一些别的C++教程,那么你可能很早就发现了,有些书上的#include命令写作# ...
- C++ 包含头文件 和 宏的使用 和 条件编译
1 #define命令剖析 1.1 #define的概念 #define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本. ...
随机推荐
- Python解析json字符串
{"status":0,"result":{"location":{"lng":116.47847460177,&quo ...
- Git工作中用法(Gitlab)
感觉又有了新的认识. 一共有3个仓库,本地自己的,远程自己的,远程主仓库. 为了方便能及时从主仓库获取更新的内容要将远程主仓库也clone下来 git clone upstream url / ...
- C++之------回调函数
一:What?(什么是回调函数) 回调函数图文讲解 谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数. 例如Win32 ...
- ServerProperties
Spring Boot 其默认是集成web容器的,启动方式由像普通Java程序一样,main函数入口启动.其内置Tomcat容器或Jetty容器,具体由配置来决定(默认Tomcat).当然你也可以将项 ...
- JSON数据解析——jsoncpp的使用
版权所有,转载请注明:http://blog.sina.com.cn/u/1978765352 由于工作中需要用到JSON数据,所以解析JSON数据就成了一个非常重要的工作内容. 其实用C++解析数据 ...
- Linux下查看Web服务器当前的并发连接数和TCP连接状态
对于web服务器(Nginx.Apache等)来说,并发连接数是一个比较重要的参数,下面就通过netstat命令和awk来查看web服务器的并发连接数以及TCP连接状态. $ netstat -n | ...
- 电机转矩T=9550*P/N推导。
很奇怪,这个公式怎么来的,原来好多是基础物理的,也许我们初中高中物理书上多有,基础真的是很基础的基础. P=F*V (1) ,即功率=力*速度 T=F*R (2) ,即力矩=力*作用长度 ,在电机里 ...
- 使用sp_configure启用 'Ad Hoc Distributed Queries'
使用sp_configure启用 'Ad Hoc Distributed Queries' 原文地址:http://blog.sina.com.cn/s/blog_531bb7630100xh88.h ...
- UIScollView Touch事件
customScrollView.h #import <UIKit/UIKit.h> @interface customScrollView : UIScrollView @end cus ...
- Struts2使用Interceptor实现权限控制的应用实例详解
Struts2使用Interceptor实现权限控制的应用实例详解 拦截器:是Struts2框架的核心,重点之重.因此,对于我们要向彻底学好Struts2.0.读源码和使用拦截器是必不可少的.少说了. ...