c++ 之类的前置声明
转自:http://blog.csdn.net/fjb2080/archive/2010/04/27/5533514.aspx 作者:清林,博客名:飞空静渡
刚开始学习c++的人都会遇到这样的问题:
定义一个类 class A,这个类里面使用了类B的对象b,然后定义了一个类B,里面也包含了一个类A的对象a,就成了这样:
- //a.h
- #include "b.h"
- class A
- {
- ....
- private:
- B b;
- };
- //b.h
- #include "a.h"
- class B
- {
- ....
- private:
- A a;
- };
一编译,就出现了一个互包含的问题了,这时就有人跳出来说,这个问题的解决办法可以这样,在a.h文件中声明类B,然后使用B的指针。
- //a.h
- //#include "b.h"
- class B;
- class A
- {
- ....
- private:
- B b;
- };
- //b.h
- #include "a.h"
- class B
- {
- ....
- private:
- A a;
- };
然后,问题就解决了。
但是,有人知道问题是为什么就被解决的吗,也就是说,加了个前置声明为什么就解决了这样的问题。下面,让我来探讨一下这个前置声明。
类的前置声明是有许多的好处的。
我们使用前置声明的一个好处是,从上面看到,当我们在类A使用类B的前置声明时,我们修改类B时,只需要重新编译类B,而不需要重新编译a.h的(当然,在真正使用类B时,必须包含b.h)。
另外一个好处是减小类A的大小,上面的代码没有体现,那么我们来看下:
- //a.h
- class B;
- class A
- {
- ....
- private:
- B *b;
- ....
- };
- //b.h
- class B
- {
- ....
- private:
- int a;
- int b;
- int c;
- };
我们看上面的代码,类B的大小是12(在32位机子上)。
如果我们在类A中包含的是B的对象,那么类A的大小就是12(假设没有其它成员变量和虚函数)。如果包含的是类B的指针*b变量,那么类A的大小就是4,所以这样是可以减少类A的大小的,特别是对于在STL的容器里包含的是类的对象而不是指针的时候,这个就特别有用了。
在前置声明时,我们只能使用的就是类的指针和引用(因为引用也是居于指针的实现的)。
那么,我问你一个问题,为什么我们前置声明时,只能使用类型的指针和引用呢?
如果你回答到:那是因为指针是固定大小,并且可以表示任意的类型,那么可以给你80分了。为什么只有80分,因为还没有完全回答到。
想要更详细的答案,我们看下下面这个类:
- class A
- {
- public:
- A(int a):_a(a),_b(_a){} // _b is new add
- int get_a() const {return _a;}
- int get_b() const {return _b;} // new add
- private:
- int _b; // new add
- int _a;
- };
我们看下上面定义的这个类A,其中_b变量和get_b()函数是新增加进这个类的。
那么我问你,在增加进_b变量和get_b()成员函数后这个类发生了什么改变,思考一下再回答。
好了,我们来列举这些改变:
第一个改变当然是增加了_b变量和get_b()成员函数;
第二个改变是这个类的大小改变了,原来是4,现在是8。
第三个改变是成员_a的偏移地址改变了,原来相对于类的偏移是0,现在是4了。
上面的改变都是我们显式的、看得到的改变。还有一个隐藏的改变,想想是什么。。。
这个隐藏的改变是类A的默认构造函数和默认拷贝构造函数发生了改变。
由上面的改变可以看到,任何调用类A的成员变量或成员函数的行为都需要改变,因此,我们的a.h需要重新编译。
如果我们的b.h是这样的:
- //b.h
- #include "a.h"
- class B
- {
- ...
- private:
- A a;
- };
那么我们的b.h也需要重新编译。
如果是这样的:
- //b.h
- class A;
- class B
- {
- ...
- private:
- A *a;
- };
那么我们的b.h就不需要重新编译。
像我们这样前置声明类A:
class A;
是一种不完整的声明,只要类B中没有执行需要了解类A的大小或者成员的操作,则这样的不完整声明允许声明指向A的指针和引用。
而在前一个代码中的语句
A a;
是需要了解A的大小的,不然是不可能知道如果给类B分配内存大小的,因此不完整的前置声明就不行,必须要包含a.h来获得类A的大小,同时也要重新编译类B。
再回到前面的问题,使用前置声明只允许的声明是指针或引用的一个原因是只要这个声明没有执行需要了解类A的大小或者成员的操作就可以了,所以声明成指针或引用是没有执行需要了解类A的大小或者成员的操作的。
转自:http://blog.csdn.net/rogeryi/archive/2006/12/12/1439597.aspx
这篇文章很大程度是受到Exceptional C++ (Hurb99)书中第四章 Compiler Firewalls and the Pimpl Idiom (编译器防火墙和Pimpl惯用法) 的启发,这一章讲述了减少编译时依赖的意义和一些惯用法,其实最为常用又无任何副作用的是使用前置声明来取代包括头文件。
Item 26 的Guideline - "Never #include a header when a forward declaration will suffice"
在这里,我自己总结了可以使用前置声明来取代包括头文件的各种情况和给出一些示例代码。
首先,我们为什么要包括头文件?问题的回答很简单,通常是我们需要获得某个类型的定义(definition)。那么接下来的问题就是,在什么情况下我们才需要类型的定义,在什么情况下我们只需要声明就足够了?问题的回答是当我们需要知道这个类型的大小或者需要知道它的函数签名的时候,我们就需要获得它的定义。
假设我们有类型A和类型C,在哪些情况下在A需要C的定义:
- A继承至C
- A有一个类型为C的成员变量
- A有一个类型为C的指针的成员变量
- A有一个类型为C的引用的成员变量
- A有一个类型为std::list<C>的成员变量
- A有一个函数,它的签名中参数和返回值都是类型C
- A有一个函数,它的签名中参数和返回值都是类型C,它调用了C的某个函数,代码在头文件中
- A有一个函数,它的签名中参数和返回值都是类型C(包括类型C本身,C的引用类型和C的指针类型),并且它会调用另外一个使用C的函数,代码直接写在A的头文件中
- C和A在同一个名字空间里面
- C和A在不同的名字空间里面
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,需要,我们需要知道调用函数的签名。
8,8的情况比较复杂,直接看代码会比较清楚一些。
C& doToC2(C& c) {return doToC(c);};
从上面的代码来看,A的一个成员函数doToC2调用了另外一个成员函数doToC,但是无论是doToC2,还是doToC,它们的的参数和返回类型其实都是C的引用(换成指针,情况也一样),引用的赋值跟指针的赋值都是一样,无非就是整形的赋值,所以这里即不需要知道C的大小也没有调用C的任何函数,实际上这里并不需要C的定义。
但是,我们随便把其中一个C&换成C,比如像下面的几种示例:
C& doToC(C&);
C& doToC2(C c) {return doToC(c);};
2.
C& doToC(C);
C& doToC2(C& c) {return doToC(c);};
3.
C doToC(C&);
C& doToC2(C& c) {return doToC(c);};
4.
C& doToC(C&);
C doToC2(C& c) {return doToC(c);};
无论哪一种,其实都隐式包含了一个拷贝构造函数的调用,比如1中参数c由拷贝构造函数生成,3中doToC的返回值是一个由拷贝构造函数生成的匿名对象。因为我们调用了C的拷贝构造函数,所以以上无论那种情形都需要知道C的定义。
9和10都一样,我们都不需要知道C的定义,只是10的情况下,前置声明的语法会稍微复杂一些。
最后给出一个完整的例子,我们可以看到在两个不同名字空间的类型A和C,A是如何使用前置声明来取代直接包括C的头文件的:
A.h
- #pragma once
- #include <list>
- #include <vector>
- #include <map>
- #include <utility>
- //不同名字空间的前置声明方式
- namespace test1
- {
- class C;
- }
- namespace test2
- {
- //用using避免使用完全限定名
- using test1::C;
- class A
- {
- public:
- C useC(C);
- C& doToC(C&);
- C& doToC2(C& c) {return doToC(c);};
- private:
- std::list<C> _list;
- std::vector<C> _vector;
- std::map<C, C> _map;
- C* _pc;
- C& _rc;
- };
- }
c++ 之类的前置声明相关推荐
- C++中前置声明介绍
前置声明是指对类.函数.模板或者结构体进行声明,仅仅是声明,不包含相关具体的定义.在很多场合我们可以用前置声明来代替#include语句. 类的前置声明只是告诉编译器这是一个类型,但无法告知类型的大小 ...
- c++ using 前置声明_C++ 类的前置声明
今天在研究C++"接口与实现分离"的时候遇到了一个问题,看似很小,然后背后的东西确值得让人深思!感觉在学习的过程中有太多的为什么,而每一个为什么背后都隐藏着一些原理和目的,所以得多 ...
- 使用类前置声明的好处-结合Qt 4一个主窗口实例讲解
本实例实现一个基本的主窗口程序,包含一个菜单条.一个工具栏.中央可编辑窗体及状态栏. 主窗口头文件代码如下: 1 #ifndef MAINWINDOW_H 2 #define MAINWINDOW_H ...
- C/C++之类的前置声明
C++中将"接口与实现分离"的两个重要目的就是"降低文件间的编译依存关系"和"隐藏对象的实现细节".而实现这一目的的关键技术就是Pimpl模 ...
- c++ using 前置声明_C++ 类声明 类前置声明范例
在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration).下面的程序中,带注释的那行就是类B的前置说明.这是必须的,因为类A中用到了类B,而类B的声明出现在类A的后面.如 ...
- c/c++ 前置声明 -- typedef问题
前几天写过前置声明的问题,不过今天写代码时又遇到了同样的问题,不过是一个typedef出来的问题. 前置声明的好处很多, 比如能避免头文件互相包含的冲突, 比如有时我们在一个头文件中只需要另一个头文件 ...
- c++ using 前置声明_详解C++ 前置声明
前置声明是C/C++开发中比较常用的技巧,主要用在三种情形: 变量/常量,例如extern int var1;; 函数,例如void foo();,注意类的成员函数无法单独做前置声明: 类,例如cla ...
- C++前置声明的一个好处与用法
2019独角兽企业重金招聘Python工程师标准>>> 在C++ GUI Qt编程中有下面的一个头文件: #ifndef FINDDIALOG_H #define FINDDIALO ...
- C++, “包含类的头文件”还是“对类进行前置声明”?
有时候,明明已经包含了类的头文件,但是还是报错"has not been declared"或者"does not name a type". 这个时候,需要对 ...
最新文章
- python 映射和反映射_python映射类型的相关介绍
- 从零开始内建你的安全测试流程
- java tongpaiyu danliantiao_java版的汉字转拼音程序
- 智慧交通day04-特定目标车辆追踪01:总览概述
- 7 php 内存泄漏_PHP 内存泄漏分析定位
- 这8种保证线程安全的技术你都知道吗?
- 2021年广东副高考试成绩查询,广东省中山市2021年高级会计师考试成绩可以查询了吗?...
- Python中出现“TabError: inconsistent use of tabs and spaces in indentation”原因和解决方法
- C语言:百鸡百钱问题
- easyui missingMessage
- node的里html中写script报错,内置对象 · TypeScript 入门教程
- C++实现播放音频文件
- 数字化营销转型,你的企业准备好了吗?
- 2021mathor cup钢材制造业中钢材切割
- 【转载】华为荣耀V9的手机录屏功能如何开启
- PS 使用画笔修复工具去除文字
- 2022-2028全球硅胶线行业调研及趋势分析报告
- 实验记录 | 为什么mtDNA的fastq数据会比对到常染色体上?
- Java体系总结 cf2e3d1401904217a28c0bd2d09c5e72
- linux服务-iptables( snat 和dnat )
热门文章
- 卸载后以前拍的视频会删除吗_可立拍!苹果自己的视频编辑App是一个被忽视的好工具...
- python缺点这么多、为什么还流行_Python这么火,为什么说它不是未来的编程语言?...
- android fragment framelayout,framelayout中fragment的切换
- php bind_param,php – 在mysqli bind_param中绑定动态变量
- python对比两个文件找出不同并显示_python difflib模块实现两个文件差异对比,并输出html格式。...
- centos7开启vnc服务_阿里云CentOS 7搭建VNC远程桌面服务步骤方法
- oracle执行sql痕迹,Oracle 查询刚执行的SQL
- linux QT 结束当前进程_Linux常用使用命令梳理
- 【LeetCode笔记】72. 编辑距离(Java、字符串、动态规划)
- run till exit from #0_华为认证hcip H13821 v2.0云服务考试真题每日一练(一)