C++ 类的前置声明
- http://www.2cto.com/kf/201311/260705.html
- 今天在研究C++”接口与实现分离“的时候遇到了一个问题,看似很小,然后背后的东西确值得让人深思!感觉在学习的过程中有太多的为什么,而每一个为什么背后都隐藏着一些原理和目的,所以得多问自己”为什么“,这样才不仅知其然,更知其所以然,才能举一反三、融会贯通,这也是我以前学习不够重视的一个问题,好在”亡羊补牢,犹未晚也!“。
C++中将”接口与实现分离“的两个重要目的就是”降低文件间的编译依存关系“和”隐藏对象的实现细节“,都是考虑到C++的高效和安全。而实现这一目的的关键技术就是”Pimpl模式(pointer to implementation)”,也即是”把一个类所有的实现细节都“代理”给另一个类来完成,而自己只负责提供接口“,而实现”Pimpl模式”的关键又是“依赖对象的声明(declaration)而非定义(definition)”,这样就引出了今天的话题:为什么通过“依赖对象的声明”可以实现Pimpl模式“,进而实现”接口与实现分离“?我们一步步来抽丝剥茧吧!什么是“前置声明”?”前置声明“和”include“就是一对冤家!我们先看一个例子:[cpp]// A.h#include "B.h"class A{public:A(void);virtual ~A(void);private:B b;};// B.h#include "A.h"class B{private:A a;public:B(void);~B(void);};一编译,就出现了一个互包含的问题了,A中有B类型的成员变量所以需要include<b.h>,而B中又有A类型的成员变量也需要include<a.h>,这就导致了循环include,编译是肯定通过不了的!解决办法就是使用”前置声明“,在A.h中加上class B的声明:[cpp]// A.h#include "B.h"class B;class A{public:A(void);virtual ~A(void);private:B b;};// B.h#include "A.h"class B{private:A a;public:B(void);~B(void);};但是,有人知道问题是为什么就被解决的吗,也就是说,加了个前置声明为什么就解决了这样的问题。下面,让我来探讨一下这个前置声明。前置声明是什么?举个形象点的例子,就是我要盖一个屋子(CHOuse),光有屋子还不行啊,我还得有床(CBed)。但是屋子还没盖好,总不能先买床吧,床的大小我定了,改天买。先得把房子盖好,盖房子的时候我先给床留个位置,等房子盖好了,我再决定买什么样的床。前置声明就是我在声明一个类(CHouse)的时候,用到了另外一个类的定义(CBed),但是CBed还没有定义呢,而且我还先不需要CBed的定义,只要知道CBed是一个类就够了。那好,我就先声明类CBed,告诉编译器CBed是一个类(不用包含CBed的头文件):class CBed;然后在CHouse中用到CBed的,都用CBed的指针类型代(因为指针类型固定大小的,但是CBed的大小只用知道了CBed定义才能确定)。等到要实现CHouse定义的时候,就必须要知道CBed的定义了,那是再包好CBed的头文件就行了。[cpp]// House.hclass CBed; // 盖房子时:现在先不买,肯定要买床的class CHouse{CBed& bed; // 我先给床留个位置// CBed bed; // 编译出错public:CHouse(void);CHouse(CBed& bedTmp);virtual ~CHouse(void);void GoToBed();};// House.cpp#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();}“前置声明”的作用?“前置声明”的作用有2:(1)解决两个class的相互依赖问题,也就是两个文件相互include,减少头文件的包含层次。比如以上的几个例子!(2)降低文件之间的“编译依存关系”,从第一个例子我们看到,当我们在类A使用类B的前置声明时,我们修改类B时,只需要重新编译类B,而不需要重新编译a.h的(当然,在真正使用类B时,必须包含b.h)。如果使用include的话,一个文件改变,所有include这个文件的文件都得重新编译,这是非常耗时的!(3)通过“前置声明”可以实现“接口与实现分离“。我们将需要提供给客户的类分割为两个classes:一个只提供接口,另一个负责实现!例如以下实例:[cpp]//XYZ.hclass X{public:X();void Print();};class Y{public:Y();void Print();};class Z{public:Z();void Print();};//XYZ.cpp#include "stdafx.h"#include "XYZ.h"#include <iostream>X::X(){}void X::Print(){std::cout<<"X::Print"<<std::endl;}Y::Y(){}void Y::Print(){std::cout<<"Y::Print"<<std::endl;}Z::Z(){}void Z::Print(){std::cout<<"Z::Print"<<std::endl;}// AImpl.h#include "stdafx.h"#include "XYZ.h"class AImpl{public:AImpl();~AImpl();X* getX();Y* getY();Z* getZ();void doSth();private:X* x;Y* y;Z* z;};//AImpl.cpp#include "stdafx.h"#include "Aimpl.h"#include <iostream>AImpl::AImpl(){x = new X();y = new Y();z = new Z();}AImpl::~AImpl(){if (x){delete x;}if (y){delete y;}if (z){delete z;}}X* AImpl::getX(){return x;}Y* AImpl::getY(){return y;}Z* AImpl::getZ(){return z;}void AImpl::doSth(){x->Print();y->Print();z->Print();}// A.h#include "stdafx.h"#include <memory>class AImpl;class X;class Y;class Z;class A{public:A();X* getX();Y* getY();Z* getZ();void doSth();private:std::shared_ptr<AImpl> pImpl;};//A.cpp#include "stdafx.h"#include "A.h"#include "Aimpl.h"A::A(){pImpl = std::shared_ptr<AImpl>(new AImpl());}X* A::getX(){return pImpl->getX();}Y* A::getY(){return pImpl->getY();}Z* A::getZ(){return pImpl->getZ();}void A::doSth(){pImpl->doSth();}//PreDeclaration.cpp#include "stdafx.h"#include "A.h"int main(){A a;a.doSth();return 0;}在这个例子中,A.h是提供给客户使用的头文件,在该文件中定义了class A,其中只含有一个指针成员(pImpl),指向其实现类(AImpl)。在这样的设计之下,使用class A的客户端就完全与X、Y、Z以及AImpl的实现细节分离了,同时实现了“实现细节”的隐藏,这样,无论这些classes的任何实现修改都不需要A的客户端重新编译。使用“前置声明”的注意事项(1)在前置声明时,我们只能使用的就是类的指针和引用,自然也就不能调用对象的方法了!像我们这样前置声明类A:class A;是一种不完整的声明,只要类B中没有执行"需要了解类A的大小或者成员"的操作,则这样的不完整声明允许声明指向A的指针和引用。而在前一个代码中的语句A a;是需要了解A的大小的,不然是不可能知道如果给类B分配内存大小的,因此不完整的前置声明就不行,必须要包含A.h来获得类A的大小,同时也要重新编译类B。再回到前面的问题,使用前置声明只允许的声明是指针或引用的一个原因是:只要这个声明没有执行“需要了解类A的大小或者成员”的操作就可以了,所以声明成指针或引用是没有“执行需要了解类A的大小或者成员的操作”的。我们将上面这个例子的A.cpp稍作修改:[cpp]//A.cpp#include "stdafx.h"#include "A.h"#include "Aimpl.h"A::A(){pImpl = std::shared_ptr<AImpl>(new AImpl());}X* A::getX(){return pImpl->getX();}Y* A::getY(){return pImpl->getY();}Z* A::getZ(){return pImpl->getZ();}void A::doSth(){//pImpl->doSth();getX()->Print();getY()->Print();getZ()->Print();}
C++ 类的前置声明的更多相关文章
- C++中前置声明介绍
前置声明是指对类.函数.模板或者结构体进行声明,仅仅是声明,不包含相关具体的定义.在很多场合我们可以用前置声明来代替#include语句. 类的前置声明只是告诉编译器这是一个类型,但无法告知类型的大小 ...
- C++中前置声明的应用与陷阱
前置声明的使用 有一定C++开发经验的朋友可能会遇到这样的场景:两个类A与B是强耦合关系,类A要引用B的对象,类B也要引用类A的对象.好的,不难,我的第一直觉让我写出这样的代码: // A.h #in ...
- C++类型前置声明
前言 本文总结了c++中前置声明的写法及注意事项,列举了哪些情况可以用前置声明来降低编译依赖. 前置声明的概念 前置声明:(forward declaration), 跟普通的声明一样,就是个声明, ...
- C++ 类声明 类前置声明范例
转载自http://www.cnblogs.com/staring-hxs/p/3244251.html 在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration).下面的 ...
- 【C++】类前置声明范例
• 在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration).下面的程序中,带注释的那行就是类B的前置说明.这是必须的,因为类A中用到了类B,而类B的声明出现在类A的后面 ...
- c++ 类前置声明【转】
[转自 here] 在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration).下面的程序中,带注释的那行就是类B的前置说明.这是必须的,因为类A中用到了类B,而类B的声明 ...
- C++类前置声明
cpp前置声明: 前置声明只能作为指针或引用,不能定义类的对象,也不能调用对象中的方法. 详见:https://www.cnblogs.com/dobben/p/7440745.html
- C++中头文件相互包含与前置声明
一.类嵌套的疑问 C++头文件重复包含实在是一个令人头痛的问题,前一段时间在做一个简单的数据结构演示程序的时候,不只一次的遇到这种问题.假设我们有两个类A和B,分别定义在各自的有文件A.h和B.h中, ...
- C++ 前置声明 和 包含头文件 如何选择
假设有一个Date类 Date.h class Date { private: int year, month, day; }; 如果有个Task类的定义要用到Date类,有两种写法 其一 Task1 ...
随机推荐
- OpenLDAP 安装及配置 笔记
首先下载 OpenLdap(Ldap服务器) 和 LdapAdmin(客户端) 两个软件 OpenLDAPforWindows_2.4.39.part1.rar OpenLDAPforWindows_ ...
- PayPal 开发详解(七):运行REST API SAMPLE
1.编译成功,修改配置文件 sdk_config.properties ,使用我们申请的测试帐号执行收款测试,clientId 和 clientSecret 参见 PayPal 开发详解(五) 2.将 ...
- 基于s5pv210嵌入式linux系统sqlite3数据库移植
基于s5pv210嵌入式linux系统sqlite3数据库移植 1.下载源码 http://www.sqlite.org/download.html 最新源码为3080100 2.解压 tar xvf ...
- 爱拼图游戏android源码完整版
这个是一款爱拼图游戏源码完整版,该游戏源码比较完整的,可以支持音乐的播放在游戏的玩的过程中,还可以控制系统的声音等,可以支持多种图片的选择来进行玩的,还可以根据自己的爱好选择不同的难度来的,级别分为: ...
- nodejs前端自动化构建
http://99jty.com/?p=1257 http://www.jankerli.com/?p=1628 http://www.cnblogs.com/zhepama/archive/2013 ...
- 一个简单且丑陋的js切换背景图片基础示例
不多说,直接上代码,非常基础的一个原生js切换元素背景图片范例 <html> <head> <meta http-equiv="Content-Type&quo ...
- %SELECTALL
If you ever need to create a view that selects all fields from a particular record, then you should ...
- [leetcode]_Balanced Binary Tree
第四道树题,逐渐能写递归了.虽然最后AC的代码还是看了网络,但是距离成功攻克此类问题仅一步之遥. 题目:一棵树,判断是否为AVL.(AVL条件:树上任意一点的左右子树的高度差<=1) 思路:树依 ...
- C# .Net三层架构[转]
C# .Net三层架构[转] 编写人:CC阿爸 2014-3-14 希望朋友们留下自己对三层架构的理解... 三层体系结构的概念 用户界面表示层(USL) 业务逻辑层(BLL) 数据访问层(D ...
- Lucene全文检索框架
1.什么时Lucene? 是一个全文搜索框架,而不是应用产品,他只是一种工具让你能实现某些产品,并不像www.baidu.com拿来就能用 是apache组织的一个用java实现的全文搜索引擎的开源项 ...