C++”前置声明“那些事儿(一)

2014-11-24 02:44:46 · 作者: · 浏览: 3
今天在研究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中又有A类型的成员变量也需要include,这就导致了循环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.h
class 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.h
class 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
X::X(){}
void X::Print(){
std::cout<<"X::Print"<
}
Y::Y(){}
void Y::Print(){
std::cout<<"Y::Print"<
}
Z::Z(){}
void Z::Print(){
std::cout<<"Z::Print"<
}
// AImpl.