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语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本. ...
随机推荐
- Sumblime Text 2 常用插件以及安装方法
1.直接安装 安装Sublime text 2插件很方便,可以直接下载安装包解压缩到Packages目录(菜单->preferences->packages). 2.使用Package C ...
- 论docker中 CMD 与 ENTRYPOINT 的区别
Dockerfile里有 CMD 与 ENTRYPOINT 两个功能咋看起来很相似的指令,开始的时候觉得两个互用没什么所谓,但其实并非如此: CMD指令: The main purpose of a ...
- Azure File文件共享(6):使用Python开发
Azure文件共享服务提供了多种方式的访问接口,包括Powershell,.Net, Java, Python等等,本章主要介绍如何使用Python来访问Azure File存储. 关于Python环 ...
- Wpf中MediaElement循环播放
原文:Wpf中MediaElement循环播放 前一段时间做了一个项目,里面牵涉到媒体文件的循环播放问题,在网上看了好多例子,都是在xaml中添加为MediaElement添加一个TimeLine,不 ...
- 自定义searchview的编辑框,搜索按钮,删除按钮,光标等
//指定某个私有属性 Field mSearchHintIconField = argClass.getDeclaredField("mSearchHintIcon"); mSea ...
- UVA_Cubic Eight-Puzzle UVA 1604
Let's play a puzzle using eight cubes placed on a 3 x 3 board leaving one empty square.Faces of cube ...
- C++静态成员函数不能调用非静态成员变量
其实我们从直观上可以很好的理解静态成员函数不能调用非静态成员变量这句话因为无论是静态成员函数还是静态成员变量,它们 都是在类的范畴之类的,及在类的整个生存周期里始终只能存在一份.然而非静态成员变量和非 ...
- Java中的native方法
博客引用地址:Java中的native方法 今天花了两个小时把一份关于什么是Native Method的英文文章好好了读了一遍,以下是我依据原文的理解. 一. 什么是Native Method 简单地 ...
- Safari HTML5 Canvas Guide: Creating Charts and Graphs
Safari HTML5 Canvas Guide: Creating Charts and Graphs Bar graphs are similar to data plots, but each ...
- JS封装cookie操作函数实例(设置、读取、删除)
本文实例讲述了JS封装cookie操作函数.分享给大家供大家参考,具体如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ...